快速排序優(yōu)化詳解

原文地址:快速排序優(yōu)化詳解

正如它的名字所體現(xiàn)盯拱,快速排序是在實(shí)踐中最快的已知排序算法祥得,平均運(yùn)行時(shí)間為O(NlogN)允青,最壞的運(yùn)行時(shí)間為O(N^2)橄碾。算法的基本思想很簡單,然而想要寫出一個(gè)高效的快速排序算法并不是那么簡單颠锉》ㄉ基準(zhǔn)的選擇,元素的分割等都至關(guān)重要琼掠,如果你不清楚如何優(yōu)化快速排序算法拒垃,本文你不該錯(cuò)過。

公眾號(hào):編程珠璣

算法思想

快速排序利用了分治的策略瓷蛙。而分治的基本基本思想是:將原問題劃分為若干與原問題類似子問題悼瓮,解決這些子問題戈毒,將子問題的解組成原問題的解。

那么如何利用分治的思想對(duì)數(shù)據(jù)進(jìn)行排序呢横堡?假如有一個(gè)元素集合A:

  • 選擇A中的任意一個(gè)元素pivot埋市,該元素作為基準(zhǔn)
  • 將小于基準(zhǔn)的元素移到左邊,大于基準(zhǔn)的元素移到右邊(分區(qū)操作)
  • A被pivot分為兩部分命贴,繼續(xù)對(duì)剩下的兩部分做同樣的處理
  • 直到所有子集元素不再需要進(jìn)行上述步驟

可以看到算法思想比較簡單道宅,然而上述步驟實(shí)際又該如何處理呢?

如何選擇基準(zhǔn)

實(shí)際上無論怎么選擇基準(zhǔn)胸蛛,都不會(huì)影響排序結(jié)果污茵,但是不同的選擇卻可能影響整體排序時(shí)間,因?yàn)榛鶞?zhǔn)選擇不同葬项,會(huì)導(dǎo)致分割的兩個(gè)集合大小不同泞当,如果分割之后,兩個(gè)集合大小是幾乎相等的民珍,那么我們整體分割的次數(shù)顯然也會(huì)減少襟士,這樣整體耗費(fèi)的時(shí)間也相應(yīng)降低。我們來看一下有哪些可選擇策略穷缤。

選擇第一個(gè)或者最后一個(gè)

如果待排序數(shù)是隨機(jī)的敌蜂,那么選擇第一個(gè)或者最后一個(gè)作基準(zhǔn)是沒有什么問題的,這也是我們最常見到的選擇方案津肛。但如果待排序數(shù)據(jù)已經(jīng)排好序的章喉,就會(huì)產(chǎn)生一個(gè)很糟糕的分割。幾乎所有的數(shù)據(jù)都被分割到一個(gè)集合中身坐,而另一個(gè)集合沒有數(shù)據(jù)秸脱。這樣的情況下,時(shí)間花費(fèi)了部蛇,卻沒有做太多實(shí)事摊唇。而它的時(shí)間復(fù)雜度就是最差的情況O(N^2)。因此這種策略是絕對(duì)不推薦的涯鲁。

隨機(jī)選擇

隨機(jī)選擇基準(zhǔn)是一種比較安全的做法巷查。因?yàn)樗粫?huì)總是產(chǎn)生劣質(zhì)的分割。
C語言實(shí)現(xiàn)參考:

ElementType randomPivot(ElementType A[],int start,int end)
{
    srand(time(NULL))
    int randIndex = rand()%(end -start)+start;
    return A[randIndex];
}

選擇三數(shù)中值

從前面的描述我們知道抹腿,如果能夠選擇到數(shù)據(jù)的中值岛请,那是最好的,因?yàn)樗軌驅(qū)⒓辖醯确譃槎ā5呛芏鄷r(shí)候很難算出中值崇败,并且會(huì)耗費(fèi)計(jì)算時(shí)間。因此我們隨機(jī)選取三個(gè)元素,并用它們的中值作為整個(gè)數(shù)據(jù)中值的估計(jì)值后室。在這里缩膝,我們選擇最左端,最右端和中間位置的三個(gè)元素的中值作為基準(zhǔn)岸霹。

假如有以下數(shù)組:

1 9 10 3 8 7 6 2 4

左端元素為1疾层,位置為0,右端元素為4松申,位置為8云芦,則中間位置為[0+8]/2=4俯逾,中間元素為8贸桶。那么三數(shù)中值就為4(1,4桌肴,8的中值)皇筛。

如何將元素移動(dòng)到基準(zhǔn)兩側(cè)

選好基準(zhǔn)之后,如何將元素移動(dòng)到基準(zhǔn)兩側(cè)呢坠七?通常的做法如下:

  • 將基準(zhǔn)元素與最后的元素交換水醋,使得基準(zhǔn)元素不在被分割的數(shù)據(jù)范圍
  • i和j分別從第一個(gè)元素和倒數(shù)第二個(gè)元素開始。i在j的左邊時(shí)彪置,將i右移拄踪,直到發(fā)現(xiàn)大于等于基準(zhǔn)的元素,然后將j左移拳魁,直到發(fā)現(xiàn)小于等于基準(zhǔn)的元素惶桐。i和j停止時(shí),元素互換潘懊。這樣就把大于等于基準(zhǔn)的移到了右邊姚糊,小于等于基準(zhǔn)的移到了左邊
  • 重復(fù)上面的步驟,直到i和j交錯(cuò)
  • 將基準(zhǔn)元素與i所指向的元素交換授舟,使得基準(zhǔn)元素將整個(gè)元素集合分割為小于基準(zhǔn)和大于基準(zhǔn)的元素集合

在們采用三數(shù)中值得方法選擇基準(zhǔn)的情況下救恨,既然基準(zhǔn)是中值,實(shí)際上只要保證左端释树,右端肠槽,中間值是從小到大即可。還是以前面提到的數(shù)組為例奢啥,我們找到三者后秸仙,對(duì)三者進(jìn)行排序如下:
排序前

1 9 10 3 8 7 6 2 4

排序后

1 9 10 3 4 7 6 2 8

如果是這樣的情況,那么實(shí)際上不需要把基準(zhǔn)元素和最后一個(gè)元素交換扫尺,而只需要和倒數(shù)第二個(gè)元素交換即可筋栋,因?yàn)樽詈笠粋€(gè)元素肯定大于基準(zhǔn),這樣可以減少交換次數(shù)正驻。

如果前面的描述還不清楚弊攘,我們看一看實(shí)際中一趟完整的流程是什么樣的抢腐。

第一步,將左端襟交,右端和中間值排序迈倍,中值作為基準(zhǔn):

1 9 10 3 4 7 6 2 8
基準(zhǔn)

第二步,將中值與倒數(shù)第二個(gè)數(shù)交換位置:

1 9 10 3 2 7 6 4 8
基準(zhǔn)

第三步捣域,i向右移動(dòng)啼染,直到發(fā)現(xiàn)大于等于基準(zhǔn)的元素9:

1 9 10 3 2 7 6 4 8
i j 基準(zhǔn)

第四步,j向左移動(dòng)焕梅,直到發(fā)現(xiàn)小于等于基準(zhǔn)的元素2:

1 9 10 3 2 7 6 4 8
i j 基準(zhǔn)

第五步迹鹅,交換i和j:

1 2 10 3 9 7 6 4 8
i j 基準(zhǔn)

第六步,重復(fù)上述步驟贞言,i右移斜棚,j左移:

1 2 10 3 9 7 6 4 8
i j 基準(zhǔn)

第七步,交換i和j指向的值:

1 2 3 10 9 7 6 4 8
i j 基準(zhǔn)

第八步该窗,重復(fù)上述步驟弟蚀,i右移,j左移酗失,此時(shí)i和j已經(jīng)交錯(cuò):

1 2 3 10 9 7 6 4 8
j i 基準(zhǔn)

第九步义钉,i和j已經(jīng)交錯(cuò),因此最后將基準(zhǔn)元素與i所指元素交換:

1 2 3 4 9 7 6 10 8
i

到這一步的時(shí)候规肴,我們發(fā)現(xiàn)i的左邊都是小于i指向的元素捶闸,而右邊都是大于i的元素。最后在對(duì)子集進(jìn)行同樣的操作即可奏纪。

如何對(duì)子集進(jìn)行排序

遞歸法

最常見的便是遞歸法了鉴嗤。遞歸的好處是代碼簡潔易懂,但是不可忽略的是序调,當(dāng)遞歸嵌套過深時(shí)醉锅,它的效率問題以及棧溢出的風(fēng)險(xiǎn)可能會(huì)迫使你選擇非遞歸法。在前面對(duì)整個(gè)集合一分為二之后发绢,對(duì)剩下的兩個(gè)集合遞歸調(diào)用硬耍,直到完成排序。簡單描述如下(非可運(yùn)行代碼):

void Qsort(int A[],int left,int right)
{
    /*分區(qū)操作*/
    int i = partition(A,left,right);
    /*對(duì)子集遞歸調(diào)用*/
    Qsort(A,left,i-1);
    Qsort(A,i+1,right);
}

遞歸最需要注意的便是遞歸結(jié)束調(diào)用边酒,否則會(huì)產(chǎn)生無限遞歸经柴,從而發(fā)生棧溢出。

后面我們會(huì)看到墩朦,遞歸法的代碼非常簡潔坯认。(相關(guān)閱讀《重新看遞歸》)

尾遞歸

在遞歸版本中,Qsort分別遞歸調(diào)用計(jì)算左右兩個(gè)子集合,而第二個(gè)遞歸其實(shí)并非必須牛哺,完全可以用循環(huán)來替代陋气,以下代碼模擬實(shí)現(xiàn)了尾遞歸,(并非是真的尾遞歸):

void Qsort(ElementType A[],int left,int right)
{
        int i = 0;
    while(left < right)
    {
            /*分割操作*/
            i = partition(A,left,right);
            /*遞歸調(diào)用*/
            Qsort(A,left,i-1);
            /*右半部分通過循環(huán)實(shí)現(xiàn)*/
            left = i + 1;
    }
}

非遞歸法

那么有沒有方法可以不用遞歸呢引润?既然遞歸每次都進(jìn)行壓棧操作巩趁,那么我們能不能分區(qū)后僅僅將區(qū)間信息存儲(chǔ)到棧里,然后從棧中取出區(qū)間再繼續(xù)分區(qū)呢淳附?顯然是可以的议慰。實(shí)際上我們每次分區(qū)時(shí),只需要知道區(qū)間即可奴曙,那么將這些區(qū)間信息存儲(chǔ)起來别凹,就可以不用遞歸了,按照分好的區(qū)間不斷分區(qū)即可缆毁。

例如對(duì)于前面提到的數(shù)組番川,首先對(duì)區(qū)間[0,8]進(jìn)行分區(qū)操作到涂,之后得到兩個(gè)新的分區(qū)脊框,1,2,3和9,7践啄,6浇雹,10,8屿讽,假設(shè)兩個(gè)區(qū)間仍然可以使用快速排序昭灵,那么需要將區(qū)間[0,2]和[5,8]的其中一個(gè)壓棧,另一個(gè)繼續(xù)分區(qū)操作伐谈。

按照這種思路烂完,代碼簡單描述如下(非可運(yùn)行代碼):

void Qsort(A,left,right)
{    
    while(STACK_IS_NOT_EMPTY)
    {
        /*分區(qū)操作*/
        POP(lo,hi);
        int mid = partition(A,lo,hi);
        /*存儲(chǔ)新的邊界*/
        PUSH(lo,mid-1);
        PUSH(mid+1,hi);
    }
}

當(dāng)然這里面沒有體現(xiàn)分區(qū)終止條件。我們需要在數(shù)據(jù)量小于一定值的時(shí)候诵棵,就不再繼續(xù)進(jìn)行分區(qū)操作了抠蚣,而是選擇插入排序(為什么?)履澳。

那么問題來了嘶窄,如何選擇棧的大小呢?查看qsort.c的源碼發(fā)現(xiàn)距贷,它選擇了如下的值:

#define STACK_SIZE (8* sizeof(unsigned long int));

為什么會(huì)是這個(gè)值呢柄冲?設(shè)想一下,假設(shè)待排序數(shù)組長度使用unsigned long int來表示忠蝗,并且假設(shè)每次都將集合分為二等分现横。那么即便數(shù)組長度達(dá)到最大值,實(shí)際上最多只需要分割8 *(sizeof(unsigned long int))次,也就將它分割完了戒祠。然而由于以下幾個(gè)原因晦攒,需要存儲(chǔ)在棧中的區(qū)間信息很難超出棧空間得哆,因?yàn)椋?/p>

  • 數(shù)組長度不會(huì)接近unsigned long int脯颜,否則內(nèi)存也撐不住了
  • 區(qū)間足夠小時(shí),不采用快速排序
  • 每做一個(gè)分區(qū)贩据,只會(huì)增加一個(gè)區(qū)間PUSH到棧中栋操,增長速度慢

注意事項(xiàng)

至此,快速排序所有的主要步驟已經(jīng)介紹完畢饱亮。但是有以下注意事項(xiàng):

  • 有大量重復(fù)元素時(shí)避免產(chǎn)生糟糕分區(qū)矾芙,因此在發(fā)現(xiàn)大于等于基準(zhǔn)或者小于等于基準(zhǔn)時(shí),便停止掃描近上。
  • 通常會(huì)將基準(zhǔn)一開始移動(dòng)到最后位置或倒數(shù)第二個(gè)位置剔宪,避免基準(zhǔn)在待分區(qū)區(qū)間。
  • 對(duì)于很小的數(shù)組(N<=20)壹无,插入排序要比快速排序更好葱绒。因?yàn)榭焖倥判蛴羞f歸開銷,并且插入排序是穩(wěn)定排序斗锭。
  • 如果函數(shù)本身的局部變量很少地淀,那么遞歸帶來的開銷也就越小岖是;如果遞歸發(fā)生棧溢出了帮毁,首先需要排除代碼設(shè)計(jì)問題。因此如果你設(shè)計(jì)的非遞歸版本效率低于遞歸版本豺撑,也不要驚訝烈疚。

注:假定在待排序的記錄序列中,存在多個(gè)具有相同的關(guān)鍵字的記錄聪轿,若經(jīng)過排序爷肝,這些記錄的相對(duì)次序保持不變,即在原序列中屹电,r[i]=r[j]阶剑,且r[i]在r[j]之前固蛾,而在排序后的序列中灿椅,r[i]仍在r[j]之前铲掐,則稱這種排序算法是穩(wěn)定的亚铁;否則稱為不穩(wěn)定的糯彬。--來自百科

遞歸版代碼實(shí)現(xiàn)

C語言代碼實(shí)現(xiàn)如下:

#include <stdlib.h>
#include <stdio.h>
#include <time.h>
/*使用快速排序的區(qū)間大小臨界值酸员,可根據(jù)實(shí)際情況選擇*/
#define MAX_THRESH 4
typedef int ElementType;
/*元素交換*/
void swap(ElementType *a,ElementType *b)
{
    ElementType temp = *a;
    *a = *b;
    *b = temp;
}
/*插入排序*/
void insertSort(ElementType A[],int N)
{
    /*優(yōu)化后的插入排序*/
    int j = 0;
    int p = 0;
    int temp = 0;
    for(p = 1;p<N;p++)
    {
        temp = A[p];
        for(j = p;j>0 && A[j-1] > temp;j--)
        {
            A[j] = A[j-1];
        }
        A[j] = temp;
    }

}
/*三數(shù)中值法選擇基準(zhǔn)*/
ElementType medianPivot(ElementType A[],int left,int right)
{
    int center = (left + right)/2 ;
    /*對(duì)三數(shù)進(jìn)行排序*/
    if(A[left] > A[center])
        swap(&A[left],&A[center]);
    if(A[left] > A[right])
        swap(&A[left],&A[right]);
    if(A[center] > A[right])
        swap(&A[center],&A[right]);
    
    /*交換中值和倒數(shù)第二個(gè)元素*/    
    swap(&A[center],&A[right-1]);
    return A[right-1];
}

/*分區(qū)操作*/
int partition(ElementType A[],int left,int right)
{
   
    int i = left;
    int j = right-1;
    /*獲取基準(zhǔn)值*/
    ElementType pivot = medianPivot(A,left,right);
    for(;;)
    {
        /*i j分別向右和向左移動(dòng)色冀,為什么不直接先i++在旱?*/
        while(A[++i] < pivot)
        {}
        while(A[--j] > pivot)
        {}
        
        if(i < j)
        {
            swap(&A[i],&A[j]);
        }
        /*交錯(cuò)時(shí)停止*/
        else
        {
            break;
        }

    }
    /*交換基準(zhǔn)元素和i指向的元素*/
    swap(&A[i],&A[right-1]);
    return i;
    
}


void Qsort(ElementType A[],int left,int right)
{
    int i = 0;
    register ElementType *arr = A;
    if(right-left >= MAX_THRESH)
    {
        /*分割操作*/
        i = partition(arr,left,right);
        /*遞歸*/
        Qsort(arr,left,i-1);
        Qsort(arr,i+1,right);
    }
    else
    {
        /*數(shù)據(jù)量較小時(shí),使用插入排序*/
        insertSort(arr+left,right-left+1);
    }
}
/*打印數(shù)組內(nèi)容*/
void printArray(ElementType A[],int n)
{
    if(n > 100)
    {
        printf("too much磨确,will not print\n");
        return;
    }
    int i = 0;
    while(i < n)
    {
        printf("%d ",A[i]);
        i++;
    }
    printf("\n");
}
int main(int argc,char *argv[])
{
    if(argc < 2)
    {
        printf("usage:qsort num\n");
        return -1;
    }
    int len = atoi(argv[1]);
    printf("sort for %d numbers\n",len);
    /*隨機(jī)產(chǎn)生輸入數(shù)量的數(shù)據(jù)*/
    int *A = malloc(sizeof(int)*len);
    int i = 0;
    srand(time(NULL));
    while(i < len)
    {
       A[i] = rand();
       i++;
    }
    printf("before sort:");
    printArray(A,len);
    /*對(duì)數(shù)據(jù)進(jìn)行排序*/
    Qsort(A,0,len-1);
    printf("after  sort:");
    printArray(A,len);
    return 0;
}

尾遞歸版代碼實(shí)現(xiàn)

略沽甥。

非遞歸版代碼實(shí)現(xiàn)

非遞歸版與遞歸版大部分代碼相同,Qsort函數(shù)有所不同乏奥,并且增加棧相關(guān)內(nèi)容定義:

/*存儲(chǔ)區(qū)間信息*/
typedef struct stack_node_t
{
    int lo;
    int hi;
}struct_node;
/*最大棧長度*/
#define STACK_SIZE 8 * sizeof(unsigned int)

/*入棧摆舟,出棧*/
#define STACK_PUSH( low, hig )  ( (top->lo = low), (top->hi = hig), top++)
#define STACK_POP( low, hig )   (top--, (low = top->lo), (hig = top->hi) )

/*快速排序*/
void Qsort( ElementType A[], int left, int right )
{
    if(NULL == A)
        return;
    /*使用寄存器指針*/
    register ElementType *arr = A;
    if ( right - left >= MAX_THRESH )
    {
        struct_node stack[STACK_SIZE]   = { { 0 } };
        register struct_node    *top            = stack;

        /*最大區(qū)間壓棧*/
        int lo = left;
        int hi = right;
        STACK_PUSH( 0, 0);
        int mid = 0;
        while ( stack < top )
        {
            /*出棧,取出一個(gè)區(qū)間進(jìn)行分區(qū)操作*/
            
            mid = partition( arr, lo, hi );

            /*分情況處理邓了,左邊小于閾值*/
            if ( (mid - 1 - lo) <= MAX_THRESH)
            {
                /* 左右兩個(gè)數(shù)據(jù)段的元素都小于閾值恨诱,取出棧中數(shù)據(jù)段進(jìn)行劃分*/
                if ( (hi - (mid+1)) <= MAX_THRESH)
                    /* 都小于閾值,從棧中取出數(shù)據(jù)段 */
                    STACK_POP (lo, hi);
                else
                    /* 只有右邊大于閾值骗炉,右邊繼續(xù)分區(qū)*/
                    lo =  mid -1 ;
            }
            /*右邊小于閾值照宝,繼續(xù)計(jì)算左邊*/
            else if ((hi - (mid+1)) <= MAX_THRESH)
                hi = mid - 1;
            /*左右兩邊都大于閾值,且左邊大于右邊句葵,左邊入棧厕鹃,右邊繼續(xù)分區(qū)*/
            else if ((mid -1 - lo) > (hi - (mid + 1)))
            {
                STACK_PUSH (lo, mid - 1);
                lo = mid + 1;
            }
            /*左右兩邊都大于閾值,且右邊大于左邊乍丈,右邊入棧剂碴,左邊繼續(xù)分區(qū)*/
            else
            {
                STACK_PUSH (mid + 1, hi);
                hi = mid  -1;
            }
        }

    }

    /*最后再使用插入排序,對(duì)于接近有序狀態(tài)的數(shù)據(jù)诗赌,插入排序速度很快*/
    insertSort(arr,right-left+1);
    
}

運(yùn)行結(jié)果

我們隨機(jī)產(chǎn)生1億個(gè)整數(shù)汗茄,并對(duì)其進(jìn)行排序:

$ gcc -o qsort qsort.c
$ time ./qsort 100000000

遞歸版運(yùn)行結(jié)果:

sort for 100000000 numbers
before sort:too much,will not print
after  sort:too much铭若,will not print

real    0m16.753s
user    0m16.623s
sys 0m0.132s

非遞歸版結(jié)果:

sort for 100000000 numbers
before sort:too much,will not print
after  sort:too much递览,will not print

real    0m16.556s
user    0m16.421s
sys 0m0.137s

可以看到叼屠,實(shí)際上兩種方法的效率差距并不是很大。至于原因绞铃,前面我們已經(jīng)說過了镜雨。

總結(jié)

本文所寫的示例實(shí)現(xiàn)與glibc的實(shí)現(xiàn)相比,還有很多可優(yōu)化的地方儿捧,例如荚坞,本文實(shí)現(xiàn)僅對(duì)int類型實(shí)現(xiàn)了排序或交換值,如果待排序內(nèi)容是其他類型菲盾,就顯得力不從心颓影,讀者可參考《高級(jí)指針話題函數(shù)指針》思考如何實(shí)現(xiàn)對(duì)任意數(shù)據(jù)類型進(jìn)行排序,懒鉴。但快速排序的優(yōu)化主要從以下幾個(gè)方面考慮:

  • 優(yōu)化基準(zhǔn)選擇
  • 優(yōu)化小數(shù)組排序效率
  • 優(yōu)化交換次數(shù)
  • 優(yōu)化遞歸
  • 優(yōu)化最差情況诡挂,避免糟糕分區(qū)
  • 元素聚合

有興趣地也可以進(jìn)一步閱讀qsort源碼碎浇,進(jìn)一步了解其中喪心病狂的優(yōu)化。

思考

  • 為什么要在遇到相同元素時(shí)就進(jìn)行掃描璃俗?
  • 插入排序最好的情況時(shí)間復(fù)雜度是多少奴璃,在什么情況下出現(xiàn)?
  • 文中實(shí)現(xiàn)的代碼還有哪些可以優(yōu)化的地方城豁?

練習(xí)

  • 采用第一種基準(zhǔn)選擇策略實(shí)現(xiàn)快速排序苟穆,并測試對(duì)有序數(shù)組的排序性能
  • 實(shí)現(xiàn)通用快速排序算法,參考《C語言函數(shù)指針

參考

  • 《數(shù)據(jù)結(jié)構(gòu)與算法分析》
  • 《算法導(dǎo)論》
  • glibc qsort.c源碼

微信公眾號(hào)【編程珠璣】:專注但不限于分享計(jì)算機(jī)編程基礎(chǔ)唱星,Linux鞭缭,C語言,C++魏颓,算法岭辣,數(shù)據(jù)庫等編程相關(guān)[原創(chuàng)]技術(shù)文章,號(hào)內(nèi)包含大量經(jīng)典電子書和視頻學(xué)習(xí)資源甸饱。歡迎一起交流學(xué)習(xí)沦童,一起修煉計(jì)算機(jī)“內(nèi)功”,知其然叹话,更知其所以然偷遗。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市驼壶,隨后出現(xiàn)的幾起案子氏豌,更是在濱河造成了極大的恐慌,老刑警劉巖热凹,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泵喘,死亡現(xiàn)場離奇詭異,居然都是意外死亡般妙,警方通過查閱死者的電腦和手機(jī)纪铺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碟渺,“玉大人鲜锚,你說我怎么就攤上這事∩慌模” “怎么了芜繁?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绒极。 經(jīng)常有香客問我骏令,道長,這世上最難降的妖魔是什么集峦? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任伏社,我火速辦了婚禮抠刺,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摘昌。我一直安慰自己速妖,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布聪黎。 她就那樣靜靜地躺著罕容,像睡著了一般。 火紅的嫁衣襯著肌膚如雪稿饰。 梳的紋絲不亂的頭發(fā)上锦秒,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音喉镰,去河邊找鬼旅择。 笑死,一個(gè)胖子當(dāng)著我的面吹牛侣姆,可吹牛的內(nèi)容都是我干的生真。 我是一名探鬼主播,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捺宗,長吁一口氣:“原來是場噩夢啊……” “哼柱蟀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚜厉,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤长已,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后昼牛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體术瓮,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年匾嘱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斤斧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡霎烙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蕊连,到底是詐尸還是另有隱情悬垃,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布甘苍,位于F島的核電站尝蠕,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏载庭。R本人自食惡果不足惜看彼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一廊佩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧靖榕,春花似錦标锄、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至星压,卻和暖如春践剂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背娜膘。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工逊脯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人竣贪。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓军洼,卻偏偏與公主長得像,于是被迫代替她去往敵國和親贾富。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歉眷,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 概述 排序有內(nèi)部排序和外部排序,內(nèi)部排序是數(shù)據(jù)記錄在內(nèi)存中進(jìn)行排序颤枪,而外部排序是因排序的數(shù)據(jù)很大汗捡,一次不能容納全部...
    蟻前閱讀 5,191評(píng)論 0 52
  • 1.插入排序—直接插入排序(Straight Insertion Sort) 基本思想: 將一個(gè)記錄插入到已排序好...
    依依玖玥閱讀 1,262評(píng)論 0 2
  • 本文是對(duì) Swift Algorithm Club 翻譯的一篇文章。Swift Algorithm Club是 r...
    Andy_Ron閱讀 539評(píng)論 0 1
  • 原文地址 快速排序 原理 快速排序是C.R.A.Hoare提出的一種交換排序畏纲。它采用分治的策略扇住,所以也稱其為分治排...
    gyl_coder閱讀 887評(píng)論 0 0
  • 為什么我還會(huì)感覺胸口疼悶?zāi)兀颗Ρ犻_眼睛盗胀,看到了眼睛紅腫的媽媽艘蹋,這時(shí)的她顯得更加滄桑了,感覺像在風(fēng)中搖曳的蘆葦票灰,隨...
    冰瞳_7444閱讀 145評(píng)論 0 0