数据结构(一)——常用的排序算法

排序算法

在这里插入图片描述

冒泡排序

  • 复杂度 n^2

  • 排序规则

冒泡排序算法的运作如下:(从后往前)
1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3. 针对所有的元素重复以上的步骤,除了最后一个。
4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

  • 稳定性:冒泡排序是一种稳定排序算法

  • 实现:

    int flag = 0;
    for(int i=length-1; i>0&&flag==0; i--)
    {
        flag = 1;
        for(int j=0;j<i;j++)
        {
            if(data[j+1] < data[j])
            {
                swap(data[j],data[j+1]);
                flag = 0;
            }
        }
    }

选择排序

  • 复杂度 n^2

  • 排序规则

    它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

  • 稳定性:选择排序是不稳定的排序方法 如:[5,5,3]

  • 实现:

    扫描二维码关注公众号,回复: 5874810 查看本文章
    int Min = 0;
    for(int i=0; i<length-1; i++)
    {
        Min = i;
        for(int j=i+1; j<length; j++)
        {
            if(data[j] < data[Min])
                Min = j;
        }
        if(Min != i)
            swap(data[Min],data[i]);
    }

直接插入排序

  • 复杂度 n^2

  • 排序规则

    每次处理就是将无序数列的第一个元素与有序数列的元素从后往前逐个进行比较,找出插入位置,将该元素插入到有序数列的合适位置中。

  • 稳定性:插入排序是稳定的

  • 实现:

    int j = 0;
    for(int i=1; i<length; i++)
    {
        if(data[i] < data[i-1])
        {
            T temp = data[i];
            for(j = i; j>0&&temp<data[j-1]; j--)
                data[j] = data[j-1];
            data[j] = temp;
        }
    }

希尔排序

  • 复杂度

  • 排序规则

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

  • 稳定性: 希尔排序是非稳定排序算法。

  • 实现:

    int i=0, j=0, k=0;
    int h = length;
    do
    {
        h = h/3 + 1;
        for(i=0;i<h;i++)
        {
            for(j=i+h; j<length; j+=h)
            {
                if(data[j] < data[j-h])
                {
                    T temp = data[j];
                    for(k=j; k>=h&&temp<data[k-h]; k-=h)
                        data[k] = data[k-h];
                    data[k] = temp;
                }
            }
        }
    }
    while(h > 1);
    

归并排序

  • 复杂度

  • 实现:

 //原地归并
 	template <typename T>
 	void Merge(T data[], int lo, int mid, int hi, T temp[])
 	{
 	    if(lo < hi)
 	    {
 	        int i_start = lo;
 	        int j_start = mid+1;
 	        int length = 0;
 	        while(i_start<=mid && j_start<=hi)
 	        {
 	            if(data[i_start]<=data[j_start])
 	            {
 	                temp[length] = data[i_start];
 	                i_start++;
 	                length++;
 	            }
 	            else
 	            {
 	                temp[length] = data[j_start];
 	                j_start++;
 	                length++;
 	            }
 	        }
 	        while(i_start<=mid)
 	        {
 	            temp[length] = data[i_start];
 	            i_start++;
 	            length++;
 	        }
 	        while(j_start<=hi)
 	        {
 	            temp[length] = data[j_start];
 	            j_start++;
 	            length++;
 	        }

 	        for(int i=0; i<length; i++)
 	            data[lo+i] = temp[i];
 	    }
 	}
 	//归并排序
 	template <typename T>
 	void MergeSort(T data[], int lo, int hi, T temp[])
 	{
 	    if(lo < hi)
 	    {
 	        int mid = (lo + hi)/2;
 	        MergeSort(data,lo,mid,temp);
 	        MergeSort(data,mid+1,hi,temp);

 	        Merge(data,lo,mid,hi,temp);
 	    }
 	}

快速排序

  • 复杂度

  • 实现:(挖坑填洞的思想,直接上代码,不难理解)

 //快速排序
 	template <typename T>
 	void QuickSort(T data[], int lo, int hi)
 	{
 	    if(hi > lo)
 	    {
 	        T temp = data[lo];
 	        int i=lo, j=hi;
 	        while(j > i)
 	        {
 	            while(i<j && data[j]>=temp)
 	                j--;
 	            if(i < j)
 	            {
 	                data[i] = data[j];
 	                i++;
 	            }

 	            while(i<j && data[i]<temp)
 	                i++;
 	            if(i < j)
 	            {
 	                data[j] = data[i];
 	                j--;
 	            }
 	        }

 	        data[i] = temp;
 	        QuickSort(data,lo,i-1);
 	        QuickSort(data,i+1,hi);
 	    }
 	}

堆排序

  • 复杂度

  • 实现:

 //大根堆调整(重点在这里)
 	template <typename T>
 	void HeapAdjust(T data[], int index, int length)
 	{
 	    int lchild = index * 2 + 1;
 	    int rchild = index * 2 + 2;
 	    int Max = index;

 	    if(lchild < length && data[lchild] > data[Max])
 	        Max = lchild;
 	    if(rchild < length && data[rchild] > data[Max])
 	        Max = rchild;
 	    if(Max != index)
 	    {
 	        swap(data[index],data[Max]);
 	        HeapAdjust(data,Max,length);
 	    }
 	}
 	//堆排序(升序使用大根堆,先构造好大根堆,然后没次将头放到尾部,并且调节结点数量减一,调节根结点……)
 	template <typename T>
 	void HeapSort(T data[], int length)
 	{
 	    int i = 0;
 	    for(i=length/2; i>=0; i--)
 	        HeapAdjust(data,i,length);

 	    for(i=length-1; i>0; i--)
 	    {
 	        swap(data[0],data[i]);
 	        HeapAdjust(data,0,i);
 	    }
 	}

总结

  • 快速排序、堆排序与归并排序的比较:

    • 快速排序是二叉查找树(二叉查找树)的一个空间最优化版本。不是循序地把数据项插入到一个明确的树中,而是由快速排序组织这些数据项到一个由递归调用所隐含的树中。这两个算法完全地产生相同的比较次数,但是顺序不同。对于排序算法的稳定性指标,原地分区版本的快速排序算法是不稳定的。其他变种是可以通过牺牲性能和空间来维护稳定性的。

    • 快速排序的最直接竞争者是堆排序(Heapsort)。堆排序通常比快速排序稍微慢,但是最坏情况的运行时间总是O(nlogn)。快速排序是经常比较快,除了introsort变化版本外,仍然有最坏情况性能的机会。如果事先知道堆排序将会是需要使用的,那么直接地使用堆排序比等待introsort再切换到它还要快。堆排序也拥有重要的特点,仅使用固定额外的空间(堆排序是原地排序),而即使是最佳的快速排序变化版本也需要Θ(logn)的空间。然而,堆排序需要有效率的随机存取才能变成可行。

    • 快速排序也与归并排序(Mergesort)竞争,这是另外一种递归排序算法,但有坏情况O(n log n)运行时间的优势。不像快速排序或堆排序,归并排序是一个稳定排序,且可以轻易地被采用在链表(linked list)和存储在慢速访问媒体上像是磁盘存储或网络连接存储的非常巨大数列。尽管快速排序可以被重新改写使用在炼串列上,但是它通常会因为无法随机存取而导致差的基准选择。归并排序的主要缺点,是在最佳情况下需要Ω(n)额外的空间。快速排序和堆排序不稳定,归并排序稳定。

  • 关于排序算法稳定性

    • 假定在待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,r[i]=r[j],且r[i]在r[j]之前,而在排序后的序列中,r[i]仍在r[j]之前,则称这种排序算法是稳定的;否则称为不稳定的。

    • 堆排序、快速排序、希尔排序、直接选择排序不是稳定的排序算法,而基数排序、冒泡排序、直接插入排序、折半插入排序、归并排序是稳定的排序算法。

    • 基数排序是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以其是稳定的排序算法。

猜你喜欢

转载自blog.csdn.net/weixin_38337616/article/details/89282081