数据结构——排序算法总结

冒泡排序

就从最为大家熟知的冒泡排序开始,
原理:
假设有一个无序数组s[n],一趟排序的过程:把第一个元素与第二个元素比较,如果第一个比第二个大,那么交换他们的位置。接着继续比较第二个元素和第三个元素的大小,如果第二个比第三个大,那么交换他们的位置,以此类推…
代码分析:
一趟排序只能将数组中的最大值挪到数组的末尾(即一次确定一个元素的最终位置),所以在每一趟排序结束后要减少待排元素个数,因此2个for循环即可搞定。

void Bubble_Sort(ElementType s[],int N)
{
    int i,j;
    for(i = N - 1; i >= 0; --i)
    {
        for ( j = 0; j < i; ++j)
        {
            if(s[j] > s[j+1])
                swap(s[j],s[j+1]);
        }
    }
}


但是这样的冒泡并不高效,如果有这么一种情况:当进行了k趟排序后(k<n),待排序列已经完全有序,但是程序依旧会继续进行余下比较,但都是无意义的,所以只需要设置一个flag,当某趟排序发生了交换时,则继续下一次比较,否则break。代码如下:

oid Bubble_Sort(ElementType s[],int N)
{
    int i,j;
    for(i = N-1; i >= 0; --i)
    {
        int flag = 0;
        for ( j = 0; j < i; ++j)
        {
            if(s[j] > s[j+1])
            {
                swap(s[j],s[j+1]);
                flag = 1;
            }
        }
        if(flag == 0) break;
    }
}

性能分析:
仅用了常数个辅助单元,因而空间复杂度为O(1)。
平均的时间复杂度为O(n^2)。
是一个稳定的算法。(补充:稳定不稳定是看对于相等的两个元素在进行排序后,相对位置是否发生了改变,没有改变则是稳定的,否则不稳定)

快速排序

原理:
快速排序是对冒泡排序的一种改进。其基本思想是基于分治的:在待排序列s[0…n-1]中,选取一个元素pivot作为基准,通过一趟排序将原表划分为两个子表,左边的值均小于pivot,右边的值均大于pivot,而pivot也处于最终位置上。而后对两个子表进行递归排序,以此类推…
代码分析:
假设我们已经有了一个划分的辅助函数Partition(),返回值为pivot在表中的最终位置,递归地调用快速排序即可,代码如下:

void Quick_Sort(ElementType s[],int low,int high)
{
    if(low < high)//递归跳出条件
    {
        int pivotPos = Partition(s,low,high);
        Quick_Sort(s,low,pivotPos-1);
        Quick_Sort(s,pivotPos+1,high);//对两个子表递归
    }
}


性能分析:

由于采用递归算法,因此需要借助一个递归工作栈来保存每一层递归调用的必要信息(如果要改成非递归算法,可以从这里出发,借用一个栈来保存信息),所以空间复杂度为O(log2n)。
时间复杂度为O(nlog2n)。

## 插入排序

插入排序有多种:直接插入排序,折半插入排序,希尔排序等。
首先来看直接插入排序:
原理:将一个已知元素s[ i ]插入到已有序的子序列s[1…i-1]当中,过程分三步:1.查找出s[ i ]在子序列s[1…i-1]中的位置k。2.将k开始往后的元素后移。3插入元素s[ i ]。
代码分析:
其实我们可以将这个过程看做打牌时抽牌的过程,一般情况下,先抽牌,再落位。代码如下:

```cpp
void Insertion_Sort(ElementType s[],int N)
{
    int i,j;
    for(i = 0;i < N;++i)
    {
        tmp = s[i];//抽牌
        for (j = i; j > 0&&s[j-1] > tmp ; --j)
            s[j] = s[j-1];//挪出空位
        s[j] = tmp;//落位
    }
}

折半插入排序

在直接插入排序中,我们可以将过程简化为查找到元素位置,并插入。在此基础上可以对查找过程进行优化,折半插入,顾名思义就是先通过折半查找,找到元素的位置,再插入即可。
代码如下:

void HalfInsertion_Sort(ElementType s[],int n)
{
    int i,j,low,mid,high;
    for(i = 2;i <= n; i++)
    {
        s[0] = s[i];
        low = 1;
        high = i - 1;
        while(low <= high)
        {
            mid = (low + high) / 2;
            if(a[mid] > s[0]) high = mid - 1;
            else low = mid + 1;
        } 
        for(j = i - 1;j > high + 1; --j)
            s[j+1] = s[j];
        s[high + 1] = s[0];
    }
}

希尔排序

希尔排序又成缩小增量排序,基本思想是:先将待排序列分割为形如s[i],s[i+d],s[i+2d],s[i+3d]…的特殊间隔子表,再对每个子表进行直接插入排序。最关键的环节就是如何确定增量序列,一个简单的选择就是d[1] = n/2,d[i+1] = d[i] / 2,并且最后一个增量为1。代码如下:

void Shell_Sort(ElementType s[],int N)
{
 int D,i;
 for(D = N / 2;D > 0; D /= 2)//步长变化
 {
  ElementType tmp = s[D];
  for (i = D; i >= D && s[i-D] > tmp; i -= D)
  {
   s[i] = s[i-D];
  }
  s[i] = tmp;
 }
}

堆排序

堆排序的特点是:将待排序列s[1…n]看作一颗完全二叉树,并且这颗二叉树是大顶堆(即s[ i ] > s[ 2i ] && s[ i ] > s[ 2i+1 ],父结点的键值大于左右孩子结点的键值)。每趟排序前,首先将序列调整为小顶堆,然后取出根结点,进行下一趟…
代码分析:大根堆适用于升序排序,小根堆适用于降序排序,按要求选择调整方式即可。
代码如下:

oid HeapAdjust(ElementType s[],int k,int n)
{
 ElementType tmp = s[k];
 int i;
 for(i = s*2; i < n;i *= 2)
 {
  if(i < n && s[i] < s[i+1])//若右孩子大于左孩子,i+1
   i++
  if(tmp > s[i])//若s[k]已经是最大值,不做操作
   break;
  s[k] = s[i];
  k = i;
 }
 k[s] = tmp;
}

void Heap_Sort(ElementType s[],int n)
{
 int i;
 for (i = n/2; i > 0; --i)
  HeapAdjust(s,i,n);
 for (int i = n; i > 1; --i)
 {
  swap(s[1],s[i]);
  HeapAdjust(s,1,i-1);
 }
 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值