八大排序算法的简单整理

这里写图片描述

插入排序

直接插入排序

插入排序:每一趟将一个待排序的记录,按照其关键字的大小插入到有序队列的合适位置里,直到全部插入完成。
原始:89 45 54 29 90
第1个数肯定是有序的。
i=1,89 45 54 29 90,j从0开始与i比较,a[j]>a[i],则交换,即 45 89 54 29 90;
i=2,45 89 54 29 90,j=0,a[j]不大于a[i]
        j=1,a[j]>a[i],则交换,45 54 89 29 90;
i=3,45 54 89 29 90,j=0,a[j]>a[i],则交换,29 54 89 45 90,
         j=1,a[j]>a[i],则交换,29 45 89 54 90,
         j=2,a[j]>a[i],则交换,29 45 54 89 90,
…………………..
当数据正序时,执行效率最好,每次插入都不用移动前面的元素,时间复杂度为O(N)。
当数据反序时,执行效率最差,每次插入都要前面的元素后移,时间复杂度为O(N^2)。
所以,数据越接近正序,直接插入排序的算法性能越好。
直接插入排序的过程中,不需要改变相等数值元素的位置,所以它是稳定的算法。

代码如下:

void InsertSort(int a[], int n)
{
    int i, j, tmp;
    //第1个数肯定是有序的,从第2个数开始遍历,依次插入有序序列
    for(i=1; i<n; i++)
    {
        tmp = a[i];
        for(j=i-1; j>=0 && a[j] > tmp; j--)
        {
            a[j+1] = a[j];  
        }
        a[j+1] = tmp;
    }
}

折半插入排入(二分插入排序)

折半插入排序:顺序地把待排序的序列中的各个元素按其关键字的大小,通过折半查找插入到已排序的序列的适当位置。
代码如下:

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

希尔排序

希尔排序的一般步骤为:
1.先取一个小于n的整数d1作为第一个增量,把文件的全部记录分成d1个组。所有距离为dl的倍数的记录放在同一个组中,在各组内进行直接插人排序。
2.取第二个增量d2<d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。
希尔排序的时间复杂度与增量的选取有关,一般认为其平均时间复杂度为
希尔排序是不稳定的算法。对于相同的两个数,可能由于分在不同的组中而导致它们的顺序发生变化。
希尔排序的速度通常比直接插入排序快。
代码如下:

void ShellSort(int a[], int n)
{
    int i, j, tmp;
    int d = n/2;
    while(d > 0)
    {
        for(i=d; i<n; i++)
        {
            tmp = a[i];
            for(j=i; j>=d && a[j-d] > tmp; j=j-d)
            {
                a[j] = a[j-d];
            }
            a[j] = tmp;
        }
        d = d/2;
    }
}

交换排序

冒泡排序

冒泡排序在扫描过程中两两比较相邻记录,如果反序则交换,最终,最大记录就被“沉到”了序列的最后一个位置,第二遍扫描将第二大记录“沉到”了倒数第二个位置,重复上述操作,直到n-1 遍扫描后,整个序列就排好序了。

时间复杂度与初始序列有关,当数据正序时,时间复杂度为O(N)。
当数据反序时,时间复杂度为O(N^2)。因而,平均时间复杂度为O(N^2)。
当i>j且a[i]==a[j]时,两者没有逆序,不进行交换,所以冒泡排序是一种稳定的排序算法。

代码如下:

void BubbleSort(int a[], int n)
{
    int i, j;
    bool exchange;
    for(i=0; i<n; i++)
    {
        exchange = false;
        for(j=n-1; j>i; j--)
        {
            if(a[j-1] > a[j])
            {
                swap(&a[j], &a[j-1]);
                exchange = true;
            }
        }
        if(!exchange)
            break;
    }
}

快速排序

该方法的基本思想是:
1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
https://blog.csdn.net/morewindows/article/details/6684558
代码如下:

int Partition(int a[], int left, int right)
{
    //随机选取基准
    int pos = (rand() % (left - right + 1) + left);
    swap(&a[pos], &a[left]);
    int X = a[left];
    int i = left, j = right;
    while(i < j)
    {
        while(i < j && a[j] >= X)
            j--;
        a[i] = a[j];
        while(i < j && a[i] <= X)
            i++;
        a[j] = a[i];
    }
    a[i] = X;
    return i;
}

//快速排序
void quickSort(int a[], int left, int right)
{
    int t = 0;
    if(left < right)
    {
        t = Partition(a, left, right);
        quickSort(a, left, t-1);
        quickSort(a, t+1, right);
    }
}

选择排序

简单选择排序

初始时在序列中找到最小(大)元素,放到序列的起始位置作为已排序序列;然后,再从剩余未排序元素中继续寻找最小(大)元素,放到已排序序列的末尾。
选择排序与冒泡排序的区别:冒泡排序通过依次交换相邻两个顺序不合法的元素位置,从而将当前最小(大)元素放到合适的位置;而选择排序每遍历一次都记住了当前最小(大)元素的位置,最后仅需一次交换操作即可将其放到合适的位置。
放到已排序序列的末尾(即交换),该操作很有可能把稳定性打乱,所以选择排序是不稳定的排序算法。时间复杂度为O(N^2)。
代码如下:

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

堆排序

每次都取堆顶的元素,将其放在序列最后面,然后将剩余的元素重新调整为最大堆,依次类推,最终得到排序的序列。
其基本思想为(大顶堆):
1、将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区
2、将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn)
3、由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
https://www.cnblogs.com/chengxiao/p/6129630.html
https://www.cnblogs.com/skywang12345/p/3602162.html
代码如下:

void HeapSort(int a[], int n)
{
    //1.构建大顶堆
    //从第一个非叶子结点开始从下至上,从右至左调整结构
    for(int i=n/2-1; i>=0; i--)
    {
        heapAdjust(a, i, n);
    }

    //2.调整堆结构+交换堆顶元素与末尾元素
    for(int i=n-1; i>=0; i--)
    {
        swap(a[i], a[0]);
        heapAdjust(a, 0, i);
    }
}

//堆的向下调整算法
/*
数组实现的堆中,第N个节点的左孩子的索引值是(2N+1),右孩子的索引是(2N+2)。
其中,N为数组下标索引值,如数组中第1个数对应的N为0。

*/
void heapAdjust(int a[], int i, int len)
{
    int tmp = a[i]; //i为当前节点的编号
    for(int k=2*i+1; k<len; k=2*k+1)
    {
        //i节点的左子结点小于右子结点,k指向右子结点
        if((k+1 < len) && (a[k] < a[k+1]))
        {
            k++;
        }
        //子节点大于父节点,将子节点值赋给父节点
        if(tmp < a[k])
        {
            a[i] = a[k];
            i = k;
        }
        else
            break;
    }
     //将temp值放到最终的位置
    a[i] = tmp;
}

归并排序

归并排序的形式就是一棵二叉树,它需要遍历的次数就是二叉树的深度,而根据完全二叉树的可以得出它的时间复杂度是O(N*lgN)。
归并排序是稳定的算法。

1、从上往下

从上往下的归并排序,基本包括3步:
① 分解 – 将当前区间一分为二,即求分裂点 mid = (low + high)/2;
② 求解 – 递归地对两个子区间a[low…mid] 和 a[mid+1…high]进行归并排序。递归的终结条件是子区间长度为1。
③ 合并 – 将已排序的两个子区间a[low…mid]和 a[mid+1…high]归并为一个有序的区间a[low…high]。
这里写图片描述

//从上往下的归并排序
void Merge(int a[], int left, int mid, int right)
{
    int len = right-left+1;
    int i = left, j = mid + 1, k = 0;
    int *tmp = (int *)malloc(sizeof(int)*len);
    while(i <= mid && j <= right)
    {
        if(a[i] < a[j])
            tmp[k++] = a[i++];
        else
            tmp[k++] = a[j++];
    }
    while(i <= mid)//复制第一段余下的部分
    {
        tmp[k++] = a[i++];
    }
    while(j <= right)//复制第二段余下的部分
    {
        tmp[k++] = a[j++];
    }
    for(i=0; i<k; i++)
    {
        a[i+left] = tmp[i];
    }
    free(tmp);
}

void merge_sort_uptodown(int a[], int start, int end)
{
    if(a == NULL || start>=end)
        return;
    int mid = (start+end) / 2;
    merge_sort_uptodown(a, start, mid);
    merge_sort_uptodown(a, mid+1, end);

    // a[start...mid] 和 a[mid...end]是两个有序空间,将它们排序成一个有序空间a[start...end]
    Merge(a, start, mid, end);
}

2、从下往上

这里写图片描述
通过”从下往上的归并排序”来对数组{80,30,60,40,20,10,50,70}进行排序时:
1. 将数组{80,30,60,40,20,10,50,70}看作由8个有序的子数组{80},{30},{60},{40},{20},{10},{50}和{70}组成。
2. 将这8个有序的子数列两两合并。得到4个有序的子树列{30,80},{40,60},{10,20}和{50,70}。
3. 将这4个有序的子数列两两合并。得到2个有序的子树列{30,40,60,80}和{10,20,50,70}。
4. 将这2个有序的子数列两两合并。得到1个有序的子树列{10,20,30,40,50,60,70,80}。
代码如下:

void Merge_group(int a[], int gap, int n)
{
    int i = 0;
    for(i=0; i+2*gap-1<n; i=i+2*gap)
    {
        Merge(a, i, i+gap-1, i+2*gap-1);
    }
    if(i+gap-1 < n-1)
        Merge(a, i, i+gap-1, n-1);
}

void merge_sort_down2up(int a[], int n)
{
    if(a == NULL || n <= 0)
        return;
    int gap;
    for(gap=1; gap<n; gap*=2)
    {
        Merge_group(a, gap, n);
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值