浅学排序算法

基础排序算法详解:冒泡、选择、插入与希尔排序

1.1 基于比较的排序

(1) 冒泡排序

比较相邻的元素并在需要时进行交换,迭代直到不再需要交换元素为止。其一个显著的 优点是可以检测序列是否有序 。当某一趟比较未发生交换时,证明序列已经有序。此时,可以跳过剩下的排序趟数提前结束排序。最好的情况即为数组有序,只遍历一次进行两两比较,无需交换。

void BubbleSort(int A[],int n)
{
    int pass,i,temp,swapped=1;
    for(pass=n-1;pass>=0&&swapped;pass--)
    {
        swapped=0;
        for(i=0;i<n-1;i++)
        {
            if(A[i]>A[i+1])
            {
                swap(A[i],A[i+1]);
                swapped=1;    
            }
        }  
    }
}

性能

  • 最坏/平均情况下的时间复杂度:o(n2)o(n^2)o(n2)
  • 最好情况下的时间复杂度(改进版):o(n)o(n)o(n)
  • 空间复杂度:o(1)o(1)o(1)

(2) 选择排序

在列表中找到最小值与当前位置的数进行交换,对所有元素重复该过程至数组有序。基于关键字比较选择,只在需要时进行交换,因此适用于关键字范围小的大数组情况。若数组有序,则无需进行交换,全过程只进行遍历元素的操作。

void SelectSort(int A[],int n)
{
    int i,j,min;
    for(i=0;i<n-1;i++)
    {
        min = i;
        for(j=i+1;j<n;j++)
        {
            if(A[j]<A[min])
                min=j;
        }
        swap(A[i],A[min]);
    }
}

性能

  • 最坏/平均情况下时间复杂度:o(n2)o(n^2)o(n2)
  • 最好情况下时间复杂度(改进版):o(n)o(n)o(n)
  • 空间复杂度:O(1)O(1)O(1)

(3) 插入排序

插入排序每次随机从输入序列中选择一个数据插入有序序列中合适的位置,直到全部插入完成序列有序为止。适合于规模不大的输入序列。

优点

  • 自适应:基本有序的情况下,时间复杂度较低
  • 在线性:输入数据和排序可以同时进行
  • 实际中比冒泡和选择排序更高效(虽然理论上平均时间复杂度都为o(n2)o(n^2)o(n2)
void InsertSort(int A[],int n)
{
    int i,j,temp;
    for(i=2;i<n;i++)
    {
        temp=A[i];
        for(j=i-1;j>=0&&A[j]>temp;j--)
        {
            A[j+1]=A[j];
        }
        A[j+1]=temp;//此处可能出现A[i]被覆盖的情况,所以一定要暂存A[i]
    }
}

性能

  • 最坏/平均情况下时间复杂度:o(n2)o(n^2)o(n2)
  • 最好情况下时间复杂度(改进版):o(n)o(n)o(n)
  • 序列基本有序情况(逆序数为d),时间复杂度:o(n+d)o(n+d)o(n+d)
  • 最坏情况下空间复杂度:o(1)o(1)o(1)

###小结
上述三种排序方式是最为基础的,理论上三者的时间复杂度一样,但在实际过程中插入排序的时间复杂度优于另外两者。应当注意上述三种算法的时间复杂度可通过 优化改进 上述代码使得在最好的情况下(原序列已有序),时间复杂度降为o(n)o(n)o(n)

(4) 希尔排序

希尔排序又称缩小增量排序/n间距(n-gap)插入排序,是一种泛化的插入排序。通过比较一定间距的元素,并逐步缩小间距,最终进行一次常规的插入排序即可(间距为1,相邻元素比较,此时需要交换的元素已非常少)。
分析
与插入排序相比,希尔排序由于间距更大可以更为快速地移动元素到目的位置,效率更高,是所有时间复杂度为o(n2)o(n^2)o(n2)的比较排序算法中最快的。但其仍然慢于归并,堆和快速排序。
在这里插入图片描述

void ShellSort(int A[],int n)
{
    int i,j,gap,temp;
    for(gap=n/2;gap>=1;gap/=2)
    {
        for(i=0;i<gap;i++)
        {
            temp=A[i];
            for(j=i+gap;j<n&&A[j]<temp;j+=gap)
                A[j-gap]=A[j];
            A[j-gap]=temp;
        }
    }
}

性能

  • 最坏/平均情况下时间复杂度:取决于间隔序列,通常优于o(n2)o(n^2)o(n2)
  • 最好情况下时间复杂度:o(n)o(n)o(n)
  • 空间复杂度:o(1)o(1)o(1)

(4) 快速排序

快速排序也称分区交换排序,采用递归调用对元素进行排序。当数组中仅有一个元素(或没有元素)需要排序时则返回;否则,选择一个元素作为枢轴点(pivot)。把数组分为两部分————一部分元素大于pivot,一部分小于pivot;对两部分数组递归调用该算法。

void QuickSort(int A[],int n)
{
    int low,high,pivot;
    if(low<high)
    {
        pivot=partition(A,low,high);
        QuickSort(A,low,pivot-1);
        QuickSort(A,pivot+1,high);
    }
}
int partition(int A[],int low,int high)
{
    int pivot=A[low],left=low,right=high;
    while(left<right)
    {
        while(A[left]<=pivot)
            left++;
        while(A[right]>pivot)
            right--;
        if(left<right)
        {
            swap(A[left],A[right]);
        }
    }
    A[low]=A[right];
    A[right]=pivot;
    return high;//思考:right为什么一定是枢轴的位置而非left
}

性能

  • 最坏情况下的时间复杂度:o(n2)o(n^2)o(n2)
  • 最好/平均情况下的时间复杂度:o(nlogn)o(nlogn)o(nlogn)
  • 空间复杂度:o(1)o(1)o(1)
  • 时间与枢轴的选取有关,因此实际中常采用随机化快速排序

(5) 归并排序

归并排序本质是将n个已排序的序列合并,得到一个整合的有序序列。通常情况下处理的是n=2的归并排序,即2路归并排序。其递归地将序列分成两部分,并对两部分分别排序,然后从最小的有序子序列开始合并得到完整的有序序列。

分析

  • 排序过程中连续访问数据,适用于链表排序
  • 对快速排序的补充:快速排序从最大子序列开始到最小子序列,需要栈结构,且不是稳定的排序算法。而归并排序从最小子序列开始,不需要栈,而且稳定。
    归并与快速排序
void MergeSort(int A[], int left, int right)
{
    if (right <= left) return;
	int mid = left + (right - left) / 2;
	MergeSort(A, left, mid);
	MergeSort(A, mid + 1, right);
	Merge(A, left, mid, right);
}
ElemType* B = (ElemType*)malloc((n + 1) * sizeof(ElemType)); 
void Merge(int A[], int left, int mid, int right)
{ 
    //A[left,..., mid]和A[mid+1,..., right]是各自有序的两段 先要将其合并为一个有序序列
    for (int i = 0; i <= right; i++)
        B[i] = A[i]; 〃将A的元素暂存到B中
    int k, w, i;
    for (k = left, w = mid + 1, i = k; k <= mid && w <= right; i++)
    {
    if(A[k] <= A[w]) 
        A[i] = B[k++];
    else
        A[i] = B[w++];
    while (k <= mid)
        A[i++] = B[k++]; //若前半部分比完之后还有剩余直接将其加在A末尾
    while (w <= right)
        A[i++] = B[w++];//若后半部分比完之后还有剩余直接将其加在A末尾
}

性能

  • 最好/最坏/平均情况下时间复杂度:o(nlogn)o(nlogn)o(nlogn)
  • 空间复杂度:o(n)o(n)o(n)

(7) 堆排序

利用堆特殊的性质可以每次从堆中得到序列中的最大值(或最小值),从而实现排序过程。因此,排序问题就转化为堆的建立问题。堆排序也属于选择排序,其实际运行效率多低于快速排序,但是最坏情况下时间复杂度也仅为o(nlogn)o(nlogn)o(nlogn)
建立堆时:当插入一个元素到堆中,需要调整堆中元素的位置,把该元素放到合适的位置上,这个过程称为堆化。以最大堆的堆化为例,首先将插入元素置于最后一个叶子节点,将其与父结点比较,若大于父节点的值则二者交换位置。重复该过程,直到该元素被放在合适的位置,即其大于两个孩子结点的值。
(下面为建立最大堆的代码示例)

Heap buildMaxHeap(int A[])
{
    Heap h;
    for(int i=0;i<n;i++)
        insert(h,A[i]);
    return h;
}
int insert (Heap h,int data)
{
    int i;
    if(h.count == h.capacity)
        ResizeHeap();//判断是否存储己满,如果己满则申清更大的存储空间
    h.count++;
    i =h.count -1;
    while(i>=0&data>h.array[(i-1)/2])/当插入值大于父结点的值
    {
    h.array[i]=h.array[(i-1)/2];
    i=(i-1)/2;
    }
    h.array[i]=data;
}

性能

  • 最坏/最好/平均情况下的时间复杂度:o(nlogn)o(nlogn)o(nlogn)
  • 空间复杂度:o(n)o(n)o(n)

(8) 树排序

树排序基于二叉搜索树进行排序,首先将给定数组的元素 创建一棵二叉搜索树 ,然后 中序遍历 该树得到有序数组。
性能

  • 最坏情况下(排序树是一棵偏斜树)时间复杂度:o(n2)o(n^2)o(n2)
  • 平均时间复杂度:o(nlogn)o(nlogn)o(nlogn)
  • 空间复杂度:o(n)o(n)o(n)

1.2 非比较排序(线性排序)

待续…

1.3 练手题(Leetcode)

待续…

(图是网上随便抱的,感谢原作者!)

参考资料:
[1] 纳拉辛哈・卡鲁曼希. 数据结构与算法经典问题解析:Java语言描述[M]. 机械工业出版社, 2016.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值