zoukankan      html  css  js  c++  java
  • 各主流排序算法详细介绍(时间/空间复杂度,适用范围和稳定性)

    一,插入排序

    插入排序基本思想:

      在一个已经有序的序列里插入新的元素,直到有序序列包含所有被排序元素。

    例子:

       

    代码实现:

    void InsertSort(vector<int> &v)
    {
        for(int i = 1;i < v.size(); ++i)//i表示有序集合里的元素数目和待插入元素下标
        {
            for(int j = i; j > 0 && v[j-1] > v[j]; --j)
            {
                int temp = v[j-1];
                v[j-1] = v[j];
                v[j] = temp;
            }
        }
    }
    View Code

    时间复杂度为O(N^2)

    空间复杂度为O(1)

    插入排序在小规模数据时或者基本有序时比较高效。

    二,希尔排序

    希尔排序基本思想:

      希尔排序是把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。本质上是分组插入排序。

    例子:

       

     在取增量d的时候一般有以下要求:

    ①最后一次排序d = 1;

    ②每次取的d并不包含处1以外的公因子;

    ③采用缩小增量法,d的取值逐步缩小。

    代码实现:

    void ShellSort(vector<int> &v)
    {
        int k = 0;
        int t = v.size() - 1;
        int delta = 2*t - k - 1;//有研究表明,delta取2*t-k-1时时间复杂度可达O(N^1.5) 
        for(k = 0;k < v.size(); ++k)
        {
            delta = (2*t-k-1) == t?(2*t-k-1):1;//最后一次delta要取1
            for(int i = 0; i < v.size()-delta; ++i)
            {
                if(v[i] > v[i+delta])
                {
                    int temp = v[i];
                    v[i] = v[i+delta];
                    v[i+delta] = temp;
                }
            }
        }
    }
    View Code

    希尔排序delta开始会很大,所以该排序不稳定。

    希尔排序的复杂度与delta相关:

    {1,2,4,8,...}使用这个增量序列的时间复杂度(最坏情形)是O(N^2)

    代码中所给序列的时间复杂度时O(N^1.5)

    空间复杂度为O(1)

    希尔排序为插入排序的一种优化,其适用范围为:大规模且无序的数据。

    三,冒泡排序

    冒泡排序基本思想:

      将第一个记录的关键字与第二个记录的关键字进行比较,若逆序则交换;然后比较第二个记录与第三个记录;依次类推,直至第n-1个记录和第n个记录比较为止——第一趟冒泡排序,结果关键字最大的记录被安置在最后一个记录上。

      对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置。重复上述过程,直到在一趟排序过程中没有进行过交换操作”为止。

    例子:

      

    代码实现:

    void BubbleSort(vector<int> &v)
    {
        for(int i = 0; i < v.size(); ++i)
        {
            for(int j = 0;j < v.size() - i; ++j)
            {
                if(v[j] < v[j-1])
                {
                    int temp = v[j];
                    v[j] = v[j-1];
                    v[j-1] = temp;
                }
            }
        }
    }
    View Code

    时间复杂度为O(N^2)

    空间复杂度为O(1)

    冒泡排序是稳定的排序算法。

    四,快速排序

    快速排序基本思想:

      快速排序的本质是分治法。从数列中挑出一个元素,称为 “基准”(pivot) 分区(partition):将序列分区,使所有元素比基准值小的放在基准前面,所有元素比基准值大的放在基准的后面(相等的数可以到任一边)(Divide) 排序:递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序(Conquer)。每次递归都会把基准放在正确的位置,即每次递归都排好一个元素,并将小于这个元素的放左边,大于这个元素的放右边。

    例子:

      记low为起始位置,high为末位置。基准一般取R[low],R[high],R[(low+high)/2]处于中间大小的数。这里叙述方便,直接取R[0]为基准。

    ①首先从后半部分开始,如果扫描到的值大于基准数据就让high减1,如果发现有元素比该基准数据的值小,就将high位置的值赋值给low位置 。

      

     ②然后开始从前往后扫描,如果扫描到的值小于基准数据就让low加1,如果发现有元素大于基准数据的值,就再将low位置的值赋值给high位置的值,指针移动并且数据交换后的结果如下:

     

     ③继续进行以上步骤,直到low >= high 停止扫描,此时基准R[0]处于正确的位置上。

     ④然后递归的处理左子序列和右子序列。

    代码实现:

    int Parition(vector<int>&v,int low,int high)
    {
        int ref = v[low];
        if(low < high)
        {
            while(low < high)
            {
                while(low < high && v[high] >= ref)
                {
                    --high;
                }
                v[low] = v[high];
                while(low < high && v[low] <= ref)
                {
                    ++low;
                }
                v[high] = v[low];
            }
            v[low] = ref;
        }
        return low;
    }
    
    void QSort(vector<int>&v,int low,int high)
    {
        if(low < high)
        {
            int index = Parition(v,low,high);
            QSort(v,low,index-1);
            QSort(v,index+1,high);
        }
    }
    
    void QuickSort(vector<int>&v)
    {
        QSort(v,0,v.size()-1);
    }
    View Code

     时间复杂度为O(NlogN)

    空间复杂度为最优的是O(logN),最差的为O(N)

     不稳定。

    五,简单选择排序

    简单选择排序基本思想:

      首先通过n-1次关键字比较,从n个记录中找出关键字最小的记录,将它与第一个记录交换;

      再通过n-2次比较,从剩余的n-1个记录中找出关键字次小的记录,将它与第二个记录交换;

      重复上述操作,共进行n-1趟排序后,排序结束。

    例子:

      

    代码实现:

    void SelectSort(vector<int>& v)
    {
        for(int i = 0; i < v.size(); ++i)
        {
            int MIN = v[i],pos = i;;
            for(int j = i; j < v.size(); ++j)
            {
                if(MIN > v[j])
                {
                    MIN = v[j];
                    pos = j;
                }
            }
            v[pos] = v[i];
            v[i] = MIN;
        }
    }
    View Code

    时间复杂度为O(N^2)

    空间复杂度为O(1)

     不稳定,交换过程中可能发生相同数字的次序颠倒。

    六,堆排序

    堆排序基本思想:

      堆是一个具有这样性质的完全二叉树:每个非终端结点(记录)的关键字大于等于(小于等于)它的孩子结点的关键字。堆排序是利用堆的特性对记录序列进行排序的一种排序方法。将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的n-1个元素重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列。因为堆是完全二叉树,所以我们可以用数组来储存。只要将需要排序的数组建立成堆,然后每次取出根结点,就把剩下的结点调整成堆;再取出根结点,如此下去,最后便能得到排好序的数据。

    例子:

     建堆:

    给定需要排序的6个数字:

    ①保证[0,0]范围内是个最大堆,12本身是个堆。

    ②保证[0,1]范围内是个最大堆,因为34 > 12,12在34的父节点上,所以需要调换12和34

    ③再保证[0,3]内是个最大堆,12 54都是34的儿子,所以调换34和54

     ④重复以上步骤,直到[0,n-1]都是个最大堆

    建堆结果是:

    调整堆并排序:

    成为堆以后那么heap[0]一定是该组数的最大值/最小值(这里建的是最大堆,所以heap[0]是最大值)。

    ①交换63和24,已知63是最大值,则63在排序后的正确位置上,我们假装数组只有n-1个数,把63踢出去。

     ②然后对[0,n-2]的所以数据进行调整,挑24左右儿子最大的与其比较,54 > 24,所以交换位置。

    然后12和31是交换位置后的24的左右儿子,又因为31 > 24交换24,31。

     这已经是一个最大堆了,将54和24交换位置(首位交换),54处在正确的位置上。

     继续重复上述过程,直到每个数都排好。

    代码实现:

    void GetHeap(vector<int>& v)//将数组v调整为最大堆
    {
        for(int i = 0; i < v.size(); ++i)
        {
            int CurrentIndex = i;//新插入的结点
            int Father = (CurrentIndex - 1) / 2;
            while(v[CurrentIndex] > v[Father])
            {
                int temp = v[CurrentIndex];
                v[CurrentIndex] = v[Father];
                v[Father] = temp;
                CurrentIndex = Father;
                Father = (CurrentIndex - 1) / 2;
            }
        }
    }
    
    void AdjustHeap(vector<int>& v,int index,int size)//交换首尾元素后调整最大堆
    {
        int right = 2*index + 2;
        int left = 2*index + 1;
        while(left < size)
        {
            int LargestIndex;
            if(v[left] < v[right] && right < size)
            {
                LargestIndex = right;
            }
            else
            {
                LargestIndex = left;
            }
            if(v[index] > v[LargestIndex])
            {
                LargestIndex = index;
            }
            if(index == LargestIndex)
            {
                break;
            }
            int temp = v[LargestIndex];
            v[LargestIndex] = v[index];
            v[index] = temp;
            index = LargestIndex;
            left = 2*index + 1;
            right = 2*index + 2;
        }
    }
    
    void HeapSort(vector<int>& v)
    {
        GetHeap(v);
        int size = v.size();
        while(size > 1)
        {
            int temp = v[0];
            v[0] = v[size - 1];
            v[size - 1] = temp;
            --size;
            AdjustHeap(v,0,size); 
        }
    }
    View Code

    时间复杂度为O(NlogN)

    空间复杂度为O(1)

    不稳定。

    七,归并排序

    归并排序基本思想:

      归并——将两个或两个以上的有序表组合成一个新的有序表,叫归并。

      2-路归并排序:将两个位置相邻的记录有序子序列归并为一个记录的有序序列。

      如果记录无序序列 R[s..t] 的两部分 R[s...(s+t)/2]   和   R[(s+t)/2+1..t] 分别按关键字有序, 则很容易将它们归并成整个记录序列是一个有序序列。具体看例子。

    例子:

      对一个序列不断递归(分治法),然后两两归并并排序。

      

    代码实现:

    void MergeArray(vector<int> &v, int left, int mid, int right, int temp[])//归并并排序
    {
        int i = left,j = mid+1;
        int n = mid,m = right;
        int k = 0;
    
        while(i <= n && j <= m)
        {
            if(v[i] < v[j])
            {
                temp[k++] = v[i++];
            }
            else
            {
                temp[k++] = v[j++];
            }
        }
        while(i <= n)
        {
            temp[k++] = v[i++];
        }
        while(j <= m)
        {
            temp[k++] = v[j++];
        }
        for(int i = 0; i < k; ++i)
        {
            v[left+i] = temp[i];
        }
    }
    
    void Divide(vector<int>& v,int left,int right,int temp[])//分治
    {
        if(left < right)
        {
            int mid = (left+right)/2;
            Divide(v,left,mid,temp);
            Divide(v,mid+1,right,temp);
            MergeArray(v,left,mid,right,temp);
        }
    }
    
    bool MergeSort(vector<int>& v)
    {
        int* temp = new int[v.size()];
        if(temp == NULL)
        {
            return false;
        }
        Divide(v,0,v.size()-1,temp);
        delete [] temp;
        return true;
    }
    View Code

    时间复杂度非常稳定,最好和最差都是O(NlogN)

    空间复杂度为O(N) (new了一个大小为n的数组)

  • 相关阅读:
    Qt与VC编程合作起龌龊
    谈谈误差补偿
    谈谈单片机编程入门方法
    小议设计模式
    创建VS工程使用神经网络库——FANN
    设计一个循环缓冲区模板
    旧文备份:FFTW介绍
    旧文备份:硬盘MBR引导记录损坏的一种修复方法
    How to disable the unused macros warning? --rust
    看阿里架构师是如何在高并发的情景下运筹帷幄
  • 原文地址:https://www.cnblogs.com/csuchenzc/p/12940416.html
Copyright © 2011-2022 走看看