基于.Net Core 2.0的平均值和样本方差的递推算法

基于.Net Core 2.0的平均值和样本方差的递推算法

1.平均值的递推算法

1.1 定义

设有一组数

其平均值定义为

(1)

也可以记为


(2)

由上述定义可以得出一个推论,如下所示:

推论1下述表达式成立


(3)

证明:由(1)可得


(4)

从而有


(5)

于是(3)成立。

1.2 代码实现

打开Visual Studio 2017,点击“文件”→“新建”→“项目”,选择控制台应用(.NET Core),如下所示:


图1  新建项目步骤1


图2  新建项目步骤2

新建一个文件,命名为RecursionCalculator.cs,在自动生成的类RecursionCalculator里面添加方法,如下所示:

代码1  递推计算类RecursionCalculator和平均值计算方法Average0

using System;

using System.Collections.Generic;


namespace RecursionCalculation

{

    public class RecursionCalculator

    {

        public static double Average0(IList data)

        {

            if(data == null)

            {

                throw new ArgumentNullException(nameof(data), "不能是空对象");

            }


            if(data.Count == 0)

            {

                return 0;

            }


            double sum = data[0];

            for(int i = 1; i < data.Count; i++)

            {

                sum += data[i];

            }


            return sum / data.Count;

        }

    }

}

在启动文件Program.cs中在类Program中添加测试方法,如下所示:

代码2  使用随机数测试平均值计算方法RecursionCalculator.Average0

using System;

using System.Collections.Generic;


namespace RecursionCalculation

{    

    class Program

    {

        static void Main(string[] args)

        {

            Test_Average0();

        }


        static void Test_Average0()

        {

            //定义初始容量

            int capacity = 10;


            //定义初始容量为capacity的int列表

            List data = new List(capacity);


            //采用当前时刻作为随机种子,定义一个随机数发生器

            Random rand = new Random((int)DateTime.Now.Ticks);


            //随机生成capacity个数字,加入列表

            for (int i = 0; i < capacity; i++)

            {

                data.Add(rand.Next(85, 100 + 1));  //添加一个[85, 100]之间的随机数

            }


            //求平均值

            double average = RecursionCalculator.Average0(data);


            //输出

            Console.Write($"{capacity}个随机数为:");

            for(int i = 0; i < capacity; i++)

            {

                Console.Write(data[i]);

                Console.Write(" ");

            }

            Console.WriteLine();

            Console.WriteLine("平均值=" + average.ToString());

        }

    }

}

输出如下:


图3  平均值计算输出结果

需要注意的是,读者的电脑中可能产生不同的输出结果。

1.3 平均值的递推计算

1.3.1 一个模拟实例——锅炉温度平均值

假设我们正在监控锅炉的温度,需要根据一段时间内的平均温度采取某种控制措施。使用温度传感器采集温度,采集周期假设1秒钟一次。现在,假设已经采集了个温度值,用来表示,则其平均温度可以用式(2)算出,具体程序代码如代码1所示。

由式(2)可知,要计算的平均值,需要对个值进行累加,计算机需要扫描个数,执行次加法操作和1次除法操作。

为了方便说明问题,我们新建一个类TemperatureCollector,模拟一个锅炉的温度采集器,并假设温度范围为[85, 100]。新建文件TemperatureCollector.cs,为类TemperatureCollector添加温度采集方法,如下所示:

代码3  温度采集器TemperatureCollector

using System;


namespace RecursionCalculation

{

    public class TemperatureCollector

    {

        private Random _rand = new Random((int)DateTime.Now.Ticks);

        public TemperatureCollector()

        {


        }


        //采集温度

        public int Collect0()

        {

            return _rand.Next(85, 100 + 1); //模拟一个[85, 100]之间的温度读数

        }

    }

}

现在,文件Program.cs中的测试方法应该改为如下所示:

代码4  计算平均温度

static void Test_Average0()

        {

            //定义初始容量

            int capacity = 10;


            //定义温度采集器

            TemperatureCollector collector = new TemperatureCollector();


            //定义初始容量为capacity的int列表

            List data = new List(capacity);            


            //随机生成capacity个数字,加入列表

            for (int i = 0; i < capacity; i++)

            {

                data.Add(collector.Collect0());

            }


            //求平均值

            double average = RecursionCalculator.Average0(data);


            //输出

            Console.Write($"{capacity}次温度读数为:");

            for(int i = 0; i < capacity; i++)

            {

                Console.Write(data[i]);

                Console.Write(" ");

            }

            Console.WriteLine();

            Console.WriteLine("平均值=" + average.ToString());

        }


图4  通过温度采集器获得平均温度


在新的采集周期里,我们获得了最新的温度值,仍然按照式(2)来计算平均值,如下所示:


(6)

如果仍然用代码4来演示,则只需修改语句int capacity = 10;为int capacity = 11;,显然,这种做法毫无乐趣,也不符合实际情况。其原因在于,我们未将数据与对象的关系处理好。现在修正一下,将温度数据保存到温度采集器里,且将温度值的数据类型改为double,以便更符合实际情况。

1)改动1:让递推计算类接受double类型的列表,改后的代码如下所示:

public class RecursionCalculator

    {

        public static double Average0(IList<double> data)

        {

            …… //其他内容不变

        }

    }

2)改动2:将采集到的温度值放在类TemperatureCollector的对象里,改后的代码如下所示:

using System;

using System.Collections.Generic;

using System.Threading;


namespace RecursionCalculation

{

    public class TemperatureCollector

    {

        //随机数发生器,模拟一个温度传感器

        private Random _rand = new Random((int)DateTime.Now.Ticks);


        //温度值列表

        private List _data = new List();


        //是否正在采集数据,true - 是,false - 否

        private bool _working = false;


        //采集周期

        private int _cycle = 1000;


        //采集线程

        private Thread _thread = null;


        public TemperatureCollector(int cycle = 1000)

        {

            if(cycle < 0)

            {

                cycle = 0;  //真实环境中应该抛出异常

            }


            if(cycle > 10000)

            {

                cycle = 10000;  //真实环境中应该抛出异常

            }


            _cycle = cycle;

        }


        //开始采集

        public void Start()

        {

            Console.WriteLine("主线程号:" + Thread.CurrentThread.ManagedThreadId.ToString());


            if(_working)

            {

                _working = false;

                _thread.Join();

                _thread = null;

            }

            _working = true;

            _data.Clear();

            _thread = new Thread(new ThreadStart(collect));

            _thread.Start();

        }


        //停止采集

        public void Stop()

        {

            if(_working)

            {

                _working = false;

                _thread.Join();

                _thread = null;

            }            

        }


        //清空当前存储的数据

        public void Clear()

        {

            _data.Clear();

        }


        //获得当前平均温度值

        public double Average()

        {

            double avg = RecursionCalculator.Average0(_data);

            return avg;

        }


        //循环采集温度     

        private void collect0()

        {

            Console.WriteLine("采集线程号collect0():" + Thread.CurrentThread.ManagedThreadId.ToString());


            while (_working)

            {

                _data.Add(_rand.Next(85, 101));

                Thread.Sleep(_cycle);

            }

        }

    }

}

3)改动3:在Program.cs中修改测试方法,改动后的代码如下:

using System;

using System.Threading;


namespace RecursionCalculation

{    

    class Program

    {

        static void Main(string[] args)

        {

            Test_TemperatureCollector_Average();

        }


        static void Test_TemperatureCollector_Average()

        {

            int count = 10;   //查看平均值的次数

            int cycle = 500;  //采集周期(毫秒)

            TemperatureCollector collector = new TemperatureCollector(cycle);


            collector.Start();


            for(int i = 0; i < count; i++)

            {

                Console.WriteLine($"最新平均温度 = {collector.Average().ToString("F2")}");

                Thread.Sleep(cycle);

            }


            collector.Stop();

        }

    }

}


图5  通过温度采集器获取平均温度

1.3.2 平均值递推计算的必要性

在小节1.3.1中,我们查看了10次平均值,每次查看平均值时,都要调用方法TemperatureCollector.Average(),在该方法里调用方法RecursionCalculator.Average0(_data)进行平均值计算。每次计算平均值时,都要将当前所有已采集到的温度值全都累加一遍。

可以想象一下,我们要实时显示当前平均温度,将温度采集周期设为100毫秒,从凌晨0:00开始采集,到10:00点时,已经采集到10×60×60×10= 360000个读数。在下一个采集值到达后,求新的平均值时,需要累加360001个温度值,这将非常耗时。当温度值个数达到某一个临界点时,求平均值所花的时长将大于采集周期,从而严重影响实时性。

当代计算机运行速度很快,360001个数据的平均值或许不算什么,但当数据个数达到上亿的时候,问题就非常明显了。

读者们想必已经知道,我们上述计算平均值的算法RecursionCalculator.Average0(_data),其时间复杂度为O(n),即为线性复杂度。对计算温度平均值而言,线性复杂度是不可忍受的,必须降低复杂度。比线性复杂度还低的复杂度,有对数复杂度log2(n)和常数复杂度O(1)。本文的目标之一,就是实现常数复杂度的平均值计算算法。

具体怎么做呢?在计算数学里面,有一种算法,叫做递推算法。简单解释如下:设f(比如温度平均值)是变量x(比如温度)的函数,变量在不同的时刻t有不同的取值,f在每个时刻的值皆与已经出现过的所有x值有关系,即

��

(7)


递推算法的大概含义是指,若要求得第n+1个函数值f_(n+1),只需要知道f_n和x_(n+1)即可,无需知道更早的数据x1,x2,……,x_n和f1,f2,……,f_(n-1)。当然,实际的递推算法,有可能涉及到比f_n和x_(n+1)更早的少数几个数据,例如x_n和f_(n-1),但这些依赖数据的具体个数是有限的,而不是随着时间的增加而增加。

在算法RecursionCalculator.Average0(_data)中,每次计算平均值时,都要依赖最新的数据和所有更早的数据,因此它不是递推算法。

1.3.3 平均值递推计算算法

由式(2)可知,


(8)

也即:


(9)

这就是平均值的递推计算公式。

现在,我们使用式(9)设计新的C#代码。由式(9)可知,新的平均值仅与上次平均值、已有数据个数、最新数据有关系,也就是说新的平均值方法要有三个参数。代码如下所示:

代码5  平均值递推算法RecursionCalculator.AverageRecursively

public static double AverageRecursively(double newestValue, int existedCount, double lastAverage)

        {

            if(existedCount < 1)

            {

                return newestValue;

            }


            double avg = lastAverage + (newestValue - lastAverage) / (existedCount + 1);

            return avg;

        }

代码6修改后的温度采集器类TemperatureCollector

……//内容不变


namespace RecursionCalculation

{

    public class TemperatureCollector

    {

        ……//内容不变


        //上次平均值

        private double _lastAverage = 0;


        ……


        //开始采集

        public void Start(int methodType = 0)

        {

            ……

            if(methodType == 0)

            {

                _thread = new Thread(new ThreadStart(collect0));

            }

            else

            {

                _thread = new Thread(new ThreadStart(collectRecursively));

            }

            ……

        }


        ……


        //清空当前存储的数据

        public void Clear()

        {

            _lastAverage = 0;

            _data.Clear();

        }


        ……


        public double AverageRecursively()

        {

            return _lastAverage;

        }


        //循环采集温度     

        private void collectRecursively()

        {

            Console.WriteLine("采集线程号collectRecursively():" + Thread.CurrentThread.ManagedThreadId.ToString());

            double newestValue = 0;

            int existedCount = 0;

 

            while (_working)

            {

                newestValue = _rand.Next(85, 101);

                _data.Add(newestValue);  //如果无需保存历史采集数据,则可以去掉该语句,以节省空间

                _lastAverage = RecursionCalculator.AverageRecursively(newestValue, existedCount, _lastAverage);

                existedCount++;

                Thread.Sleep(_cycle);

            }

        }

    }

}

测试方法如下:

……

static void Main(string[] args)

        {

            //Test_TemperatureCollector_Average();

            Test_TemperatureCollector_AverageRecursively();

        }


static void Test_TemperatureCollector_AverageRecursively()

        {

            int count = 10;   //查看平均值的次数

            int cycle = 500;  //采集周期(毫秒)

            TemperatureCollector collector = new TemperatureCollector(cycle);

            collector.Start(1);

            for (int i = 0; i < count; i++)

            {

                Console.WriteLine($"最新平均温度 = {collector.AverageRecursively().ToString("F2")}");

                Thread.Sleep(cycle);

            }

        }




图6  递推计算平均温度

1.2.4 性能比较

(一)理论分析

线性算法求平均值使用的公式是式(2),每求一次平均值,需要执行n-1次加法和1次除法(假设相当于1次加法)。若要在温度循环采集过程中实时显示平均温度,则第n次温度采集后计算平均值需要n-1次加法,列表如下:


递推算法求平均值使用的是公式(9),每求一次平均值,需要执行2次加法,1次减法(假设相当于1次加法)和1次除法(假设相当于1次加法),列表如下:


由此可见,使用线性算法实时求取平均值的时间复杂度是O(n2),而递推算法的时间复杂度是O(n)。


(二)实际测试

在这里,我们将对线性算法和递推算法求平均值的性能进行实际测试,为此,需要做以下几点改动:

(1)为了避免无关因素的干扰,将避开类TemperatureCollector,而直接调用类RecursionCalculator的相关方法。

(2)为了保证两种算法使用的是同一种数据,我们在每次求平均值之前,生成一份随机数据,该份数据同时用于线性算法和递推算法。

相关代码改动如下:

1)为类RecursionCalculator增加新的方法,如下所示:

//求data列表中前count个数据的平均值

        public static double Average0_static(IList data, int count)

        {

            if (data == null)

            {

                throw new ArgumentNullException(nameof(data), "不能是空对象");

            }


            if (data.Count == 0)

            {

                return 0;

            }


            double sum = data[0];

            for (int i = 1; i < Math.Min(data.Count, count); i++)

            {

                sum += data[i];

            }


            return sum / data.Count;

        }


2)在Program.cs文件中,为类Program增加两个字段和两个方法:

static Dictionary linearElapsedTime = null;

        static Dictionary recursiveElapsedTime = null;


static void Test_TemperatureCollector_AverageAndAverageRecursively_Performance()

        {

            int interval = 2000;

            int testCount = 18;

            linearElapsedTime = new Dictionary(testCount);

            recursiveElapsedTime = new Dictionary(testCount);


            for (int i = 1; i <= testCount; i++)

            {

                Test_TemperatureCollector_AverageAndAverageRecursively(interval * i);

            }

            string filePath = @"D:\test.txt";

            StreamWriter file = File.CreateText(filePath);

            string s = "";

            file.WriteLine("线性算法求平均值所耗时间:求值次数     时间(毫秒)");

            foreach (int capacity in linearElapsedTime.Keys)

            {

                s = "";

                s = capacity.ToString() + "    " + linearElapsedTime[capacity].ToString();

                file.WriteLine(s);

            }

            file.WriteLine();

            file.WriteLine();


            file.WriteLine("递推算法求平均值所耗时间:求值次数     时间(毫秒)");

            foreach (int capacity in recursiveElapsedTime.Keys)

            {

                s = "";

                s = capacity.ToString() + "    " + recursiveElapsedTime[capacity].ToString();

                file.WriteLine(s);

            }

            file.Close();

        }


static void Test_TemperatureCollector_AverageAndAverageRecursively(int capacity)

        {

            Stopwatch w = new Stopwatch();

            List data = new List(capacity);

            Random rand = new Random((int)DateTime.Now.Ticks);

            for(int i = 0; i < capacity; i++)

            {

                data.Add(rand.Next(85, 101));

            }


            double avg = 0;


            #region 线性算法求平均值

            w.Start();

            for (int i = 1; i <= capacity; i++)

            {

                avg = RecursionCalculator.Average0_static(data, i);

            }

            w.Stop();

            linearElapsedTime.Add(capacity, w.ElapsedMilliseconds);

            #endregion



            #region 递推算法求平均值

            avg = 0;            

            w.Restart();

            for (int i = 0; i < capacity; i++)

            {

                avg = RecursionCalculator.AverageRecursively(data[i], i, avg);

            }

            w.Stop();

            recursiveElapsedTime.Add(capacity, w.ElapsedMilliseconds);

            #endregion

        }

最终输出的文件内容,如下表所示:


1.4结论

可见,基于递推算法的平均值计算,其性能要比普通的线性算法好的多。

[if !supportLists]2. [endif]样本方差的递推算法

2.1 定义

设有一组数,其总体方差定义为


(10)

其中为σ^2总体方差,n为总体例数。由定义可见,σ^2描述了一组数据与均值之间的偏离程度,σ^2越大,说明这组数据的越不一致。不过,由于σ^2取的是平方,而样本值x和均值μ皆为一次方,二者不方便比较,因此通常对σ^2开平方,其结果σ称为总体标准差。

举个例子,张三和李四均为射箭运动员,他们分别射出5箭,成绩如下:


由上表可以看出,两人的平均成绩一致,难分伯仲,但张三的总体标准差为1.62,李四的则为0.49,显然李四的成绩更为稳定。通俗地说,每次射箭前,可以预测张三所得的环数与平均值差别大概在1.62环,而李四则为0.49环,可见,李四的发挥更为稳定,而张三发挥的波动较大。

在现实中,通常很难获得某个统计指标的全部数据,例如,很难获得世界上每一个人的身高,只能获取部分样本的数据,例如只能抽查获得少数个体的身高,因此,就改用样本方差和样本标准差来计算,定义如下:


(11)

当n=1时,定义σ^2的值为0。

2.2 样本方差的递推计算

2.2.1 公式推导

由式(11)可知


(12)

最终结果为


(13)

由式(13)可知,第个样本方差只与上次方差σ^2_n、已有样本数n、上次均值μ_n、最新数据x_(n+1)和最新均值μ_(n+1)有关,而由式(9)可知,新的平均值μ_(n+1)仅与上次平均值μ_n、已有样本数n、最新数据x_(n+1)有关系,因此,最新的样本方差与历史数据没有关系,它只与上次方差σ^2_n、已有样本数n、上次均值μ_n和最新数据x_(n+1)有关系。

2.2.2 代码实现

1)在文件RecursionCalculator.cs中为类RecursionCalculator新增两个方法,如下所示:

//线性算法计算样本方差

        public static double Variance0(IList data)

        {

            double variance = 0;

            if (data == null)

            {

                throw new ArgumentNullException(nameof(data), "不能是空对象");

            }


            if (data.Count < 2)

            {

                return 0;

            }


            double avg = Average0(data);

            double sum = 0;

            for(int i = 0; i < data.Count; i++)

            {

                sum += (data[i] - avg) * (data[i] - avg);

            }

            variance = sum / (data.Count - 1);

            return variance;

        }


        //递推算法计算样本方差

        public static double VarianceRecursively(double newestValue, int existedCount, double lastAverage, double lastVariance, double newestAverage)

        {

            if(existedCount < 2)

            {

                return 0;

            }


            double variance = 0;

            double tmp = (newestValue - lastAverage) / (existedCount + 1);

            variance = (existedCount - 1) * lastVariance / existedCount + tmp * tmp + (newestValue - newestAverage) / existedCount;


            return variance;

        }

2)在TemperatureCollector.cs中为类TemperatureCollector新增字段,如下所示:

//上次样本方差

        private double _lastVariance = 0;

3)在TemperatureCollector.cs中为类TemperatureCollector在方法public void Start(int methodType = 0)中的语句_lastAverage = 0;后面添加如下代码:

_lastVariance = 0;

4)在TemperatureCollector.cs中为类TemperatureCollector增加两个方法:

//线性算法计算温度数据集的样本方差

        public double Variance0()

        {

            double variance = RecursionCalculator.Variance0(_data);

            return variance;

        }


        //递推算法计算温度数据集的样本方差

        public double VarianceRecursively()

        {

            return _lastVariance;

        }

4)在TemperatureCollector.cs中为类TemperatureCollector修改方法private void collectRecursively(),改后如下所示:

private void collectRecursively()

        {

            Console.WriteLine("采集线程号collectRecursively():" + Thread.CurrentThread.ManagedThreadId.ToString());

            double newestValue = 0;

            int existedCount = 0;

            double tmp = 0;


            while (_working)

            {

                newestValue = _rand.Next(85, 101);

                _data.Add(newestValue);  //如果无需保存历史采集数据,则可以去掉该语句,以节省空间

                tmp = _lastAverage;

                _lastAverage = RecursionCalculator.AverageRecursively(newestValue, existedCount, _tmp );

                _lastVariance = RecursionCalculator.VarianceRecursively(newestValue, existedCount, tmp, _lastVariance, _lastAverage);

                existedCount++;

                Thread.Sleep(_cycle);

            }

        }

5)在文件Program.cs中为类Program增加方法,如下所示:

static void Test_TemperatureCollector_VarianceAndVarianceRecursively()

        {

            int count = 10;   //查看样本方差的次数

            int cycle = 500;  //采集周期(毫秒)

            TemperatureCollector collector = new TemperatureCollector(cycle);


            #region 使用线性算法计算样本方差

            collector.Start(0);

            for (int i = 0; i < count; i++)

            {

                Console.WriteLine($"线性算法计算最新样本方差 = {collector.Variance0().ToString("F2")}");

                Thread.Sleep(cycle);

            }


            collector.Stop();

            #endregion


            #region 使用递推算法计算样本方差

            collector = new TemperatureCollector(cycle);

            collector.Start(1);

            for (int i = 0; i < count; i++)

            {

                Console.WriteLine($"递推算法计算最新样本方差 = {collector.VarianceRecursively().ToString("F2")}");

                Thread.Sleep(cycle);

            }


            collector.Stop();

            #endregion

        }

6)在文件Program.cs中为类Program修改方法static void Main(string[] args),改后如下所示

//Test_TemperatureCollector_Average();

            //Test_TemperatureCollector_AverageRecursively();   

            //Test_TemperatureCollector_AverageAndAverageRecursively_Performance();

            Test_TemperatureCollector_VarianceAndVarianceRecursively();




输出结果如下:


图7  线性算法和递推算法计算样本方差


3. 结论

本文讨论了平均值和样本方差的递推算法实现,论证其效果比线性算法好很多,并给出了基于.Net Core 2.0的C#实现。

由于本人学识有限,文中内容可能存在不少错误,请指正。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容