【排序方法总结 】希尔排序 快速排序 归并排序 堆排序等

常见的七种排序算法:

  • 外排序:需要在内外存之间多次交换数据才能进行
  • 内排序: 
              插入类排序 
                     直接插入排序
                     希尔排序
              选择类排序 
                     简单选择排序
                     堆排序
              交换类排序 
                     冒泡排序
                     快速排序
              归并类排序 
                    归并排序

归并排序为什么比冒泡快?相比归并,冒泡多了哪些不必要的比较?

插入排序

从待排序的n个记录中的第二个记录开始,依次与前面的记录比较并寻找插入的位置,每次外循环结束后,将当前的数插入到合适的位置。

void sort_maopao(int a[],int len) {
	int temp;
	for (int i = 0;i < len -1;i++) {
		for (int j = 0;j < len - 1 - i;j++) {
			if (a[j] > a[j + 1]) {
				temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
			//swap(a[j],a[j+1]);
		}
	}
}

Shell 排序(希尔排序)

Shell 排序又称缩小增量排序, 是对直接插入排序的改进,效率提高。对相邻指定距离(称为增量)的元素进行比较,并不断把增量缩小至1,完成排序

算法实现

void sort_insert(int a[], int len)
{
	int i,j,gap;
	int temp;
	for (gap = len / 2; gap > 0; gap /= 2) {
		//从数组第gap个元素开始
		for (i = gap; i < len; i++) {
			//每个元素与自己组内的数据进行直接插入排序
			if (a[i] < a[i - gap]){
				temp = a[i];
				j = i - gap;
				while (j >= 0 && a[j] > temp)
				{
					a[j + gap] = a[j];
					j -= gap;
				}
				a[j + gap] = temp;
			}
		}
	}
}


冒泡排序

基本思想就是两两比较相邻的元素,如果逆序,则交换位置,最后最小的元素就像气泡一样浮到最上面。为了避免对已然有序的数据集一直执行比较操作,可以设置标志位flag控制循环,如果某次循环存在元素交换,设flag = 1,表明下次仍然需要继续循环比较;如果false = 0,表明剩余元素已然有序,此时排序工作结束。

改进后的冒泡排序最好情况下,只需要n-1次比较,时间复杂度为O(n);在数据集为逆序时,情况最糟糕,为O(n2)

算法实现:

#include<stdio.h>
void main(){
    int i, j, a[11];
    printf("请输入10个数字\n");
    for (i = 1; i < 11; i++)
        scanf("%d", &a[i]);
    for (i = 1; i < 10;i++)
        for (j = 1; j < 10 - i;j++)
        if a[j]>a[j+1]{
            int t = a[j];
            a[j] = a[j + 1];
            a[j + 1] = t;
        }
        printf("排序后的顺序为:\n");
        for (i = 1; i <= 10; i++)
            printf("%5d", a[i]);

}

快速排序

快速排序方法效率较高,为O(NlogN),并且快速排序的思想为– 分治法,在找工作面试时经常会被提及。
快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序。它采用了一种分治的策略,通常称其为分治法(Divide-and-ConquerMethod)。 
该方法的基本思想是: 
1.先从数列中取出一个数作为基准数。 
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。 
3.再对左右区间重复第二步,直到各区间只有一个数。

举例说明:现有十组数据需要重新排序,a[10]={23,43,2,5,1,87,53,90,76,68}. 
首先取最开始的一个数为基准数,即令i=1,j=10,k=23.
# include<stdio.h>
void qusort(int s[], int start, int end){
    int i, j;
    i = start;
    j = end;
    s[0] = s[start];
    while (i < j){
        while (i < j && s[0] < s[j])
            j--;
        while (i < j && s[0] >= s[0])
            i++;
        if (i < j){
            s[j] = s[i];
            j--;
        }
    }
    s[i] = s[0];
    if (start < i)
        qusort(s, start, j - 1);
    if (i < end)
        qusort(s, j + 1, end);
}

void main(){
    int a[11], i;
    printf("请输入10个数据\n");
    for (i = 1; i < 11;i++)
    scanf("%d", &a[i]);
    qusort(a, 1, 10);
    printf("排序之后的数据为:");
    for (i = 1; i <= 10; i++)
        printf("%5d", a[i]);
    printf("\n");
}

选择排序

从所有记录中选出最小的一个数据元素与第一个位置的记录交换;然后在剩下的记录当中再找最小的与第二个位置的记录交换,循环到只剩下最后一个数据元素为止。因此,其时间复杂度为O(n2)

算法实现

基本思想就是在第num次循环中找出最小的元素与pd[num]进行交换,num范围为[0, size -1),因为只剩下最后一个元素时,不必再进行选择了。第num次循环中,需要size – 1 – num次比较操作。

int select_sort(void* data, int size, int esize,
                int (*compare)(const void* key1, const void* key2))
{
    char*   pd = (char*)data;
    void*   ptemp;
    int num, i, min;

    if((ptemp = (char*)malloc(esize)) == NULL)
        return -1;

    for(num = 0; num < size - 1; num++)
    {

        min = num;          /* 假设索引为num的元素值最小 */
        for(i = num + 1; i < size; i++)
        {
            if(compare(&pd[min * esize], &pd[i * esize]) > 0)
                min = i;
        }
        if(compare(&min, &num) != 0)
        {
            memcpy(ptemp, &pd[num * esize], esize);
            memcpy(&pd[num * esize], &pd[min * esize], esize);
            memcpy(&pd[min * esize], ptemp, esize);
        }
    }
    free(ptemp);

    return 0;
}


堆排序

  • 算法描述

作为选择排序的改进版,堆排序可以把每一趟元素的比较结果保存下来,以便我们在选择最小/大元素时对已经比较过的元素做出相应的调整。

      堆排序是一种树形选择排序,在排序过程中可以把元素看成是一颗完全二叉树,每个节点都大(小)于它的两个子节点,当每个节点都大于等于它的两个子节点时,就称为大顶堆,也叫堆有序;当每个节点都小于等于它的两个子节点时,就称为小顶堆.

                       

                          大顶堆(有序堆)                                                                        小顶堆 

  • 算法思想(以大顶堆为例):
1.将长度为n的待排序的数组进行堆有序化构造成一个大顶堆, 此堆为初始的无序区
 
2.将根节点与尾节点交换并输出此时的尾节点
 
3.将剩余的n -1个节点重新进行 堆有序化
 

4.重复步骤2,步骤3直至构造成一个有序序列


 图片来源:https://www.cnblogs.com/0zcl/p/6737944.html

堆排序前42是在42后面,排序后42在42前面,因此堆排序是不稳定的


  • 算法实现
def sift_down(array, start, end):
    """
    调整成大顶堆,初始堆时,从下往上;交换堆顶与堆尾后,从上往下调整
    :param array: 列表的引用
    :param start: 父结点
    :param end: 结束的下标
    :return: 无
    """
    # 当列表第一个是以下标0开始,结点下标为i,左孩子则为2*i+1,右孩子下标则为2*i+2;
    # 若下标以1开始,左孩子则为2*i,右孩子则为2*i+1
    left_child = 2*start + 1  # 左孩子的结点下标
    # 当结点的右孩子存在,且大于结点的左孩子时
    if left_child+1 <= end and array[left_child+1] > array[left_child]:
        left_child += 1
    if array[left_child] > array[start]:  # 当左右孩子的最大值大于父结点时,则交换
        temp = array[left_child]
        array[left_child] = array[start]
        array[start] = temp

    print(">>", array)


def heap_sort(array):  # 堆排序
    # 先初始化大顶堆
    first = len(array)//2 -1  # 最后一个有孩子的节点(//表示取整的意思)
    # 第一个结点的下标为0,很多博客&课本教材是从下标1开始,无所谓吧,你随意
    for i in range(first, -1, -1):  # 从最后一个有孩子的节点开始往上调整
        print(array[i])
        sift_down(array, i, len(array)-1)  # 初始化大顶堆

    print("初始化大顶堆结果:", array)

if __name__ == "__main__":
    array = [98, 86, 68, 58, 42, 42]
    print(array)
    heap_sort(array)
    print(array)

归并排序

  • 算法描述:
归并排序是另一种利用 分治法排序的算法,其首先将数据集二等分为左右两个区间,分别在左右区间上递归地使用归并排序算法,然后将排序后的两个区间合并为一个有序区间。与快速排序一样,它依赖于元素之间的比较。 归并排序的归并过程就是合并两个已经排好序的数据集合,此时对需要合并的元素遍历一次即可,非常高效。因此,归并排序在所有的情况下都能达到快速排序的平均性能。但是,其主要的 缺点是排序过程需要额外的存储空间来支持,因为 合并过程不能在无序数据集本身中进行,因此需要两倍于无序数据集的空间来运行算法,这也就极大地降低了实际中使用归并排序的频率,反而由快速排序取代它的工作。接口中的lpos、dpos、rpos分别表示一个区间的左端、中间、右端位置。

  • 算法分析:核心排序

归并排序本质上就是将一个无序数据集分割成许多只包含一个元素的集合,然后按序将这些小集合合并为大集合,直到生成一个最大的有序数据集为止,因此,归并算法的时间复杂度为O(nlgn),此外,在不进行算法改进的情况下,需要两倍于无序数据集的存储空间,O(n)

比较抽象,我们一起来看归并排序的处理元素的图例:


算法实现

#include <iostream>
using namespace std;


void merge(int arr[],int l,int mid,int r)
{
    int aux[r-l+1];//开辟一个新的数组,将原数组映射进去 
    for(int m=l;m<=r;m++)
    {
        aux[m-l]=arr[m];
    }
    
    int i=l,j=mid+1;//i和j分别指向两个子数组开头部分
    
    for(int k=l;k<=r;k++)
    {
        if(i>mid)
        {
            arr[k]=aux[j-l];
            j++;
        }
        else if(j>r)
        {
            arr[k]=aux[i-l];
            i++;
        }
                else if(aux[i-l]<aux[j-l])
                {
                    arr[k]=aux[i-l];
                    i++;    
                }
                else
                {
                    arr[k]=aux[j-l];
                    j++;
                }
    } 
} 
//递归的使用归并排序,对arr[l....r]排序 
void merge_sort(int arr[],int l,int r)
{
    if(l >=r)
        return ;
    int mid=(l+r)/2; 
    merge_sort(arr,l,mid);
    merge_sort(arr,mid+1,r);
    merge(arr,l,mid,r);
}

void my_merge_sort(int arr[],int n)
{
    merge_sort(arr,0,n-1);    
} 

int main()
{
    int a[6];
    for(int i=0;i<6;i++)
    {
        cin>>a[i];
    }
    my_merge_sort(a,6);
    for(int i=0;i<6;i++)
    {
        cout<<a[i]<<" ";
    }
    return 0;
}

基数排序

  • 算法描述:
基数排序是另外一种高效的线性排序方法。其将数据按位分离,并从数据的最低有效位到最高有效位依次进行排序,从而得到有序数据集合。但是,对某个位进行排序时必须选择稳定的排序算法。基数排序并不局限于对整型数据进行排序,凡是将元素分割为整型的,就可以使用它。基数的选择依赖于数据本身,例如以28为基对字符串进行排序,以10为基对整型进行排序...。bits表示每个待排元素包含的位数,radix表示基数。
  • 算法分析:

基数排序实质上就是对元素的每一位进行计数排序,因此其时间复杂度为O(b(n + r)),其中n为数据集中元素个数,r为基数radix,b为每个元素的位数bits;空间复杂度与计数排序一样,需要额外的temp和counts,大致可以记为O(n)

首先将所有待比较树统一为统一位数长度,接着从最低位开始,依次进行排序:

1. 按照个位数进行排序。
2. 按照十位数进行排序。
3. 按照百位数进行排序。

排序后,数列就变成了一个有序序列。


算法实现

/*
 * 获取数组a中最大值
 *
 * 参数说明:
 *     a -- 数组
 *     n -- 数组长度
 */
int get_max(int a[], int n)
{
    int i, max;

    max = a[0];
    for (i = 1; i < n; i++)
        if (a[i] > max)
            max = a[i];
    return max;
}

/*
 * 对数组按照"某个位数"进行排序(桶排序)
 *
 * 参数说明:
 *     a -- 数组
 *     n -- 数组长度
 *     exp -- 指数。对数组a按照该指数进行排序。
 *
 * 例如,对于数组a={50, 3, 542, 745, 2014, 154, 63, 616};
 *    (01) 当exp=1表示按照"个位"对数组a进行排序
 *    (02) 当exp=10表示按照"十位"对数组a进行排序
 *    (03) 当exp=100表示按照"百位"对数组a进行排序
 *    ...
 */
void count_sort(int a[], int n, int exp)
{
    int output[n];             // 存储"被排序数据"的临时数组
    int i, buckets[10] = {0};

    // 将数据出现的次数存储在buckets[]中
    for (i = 0; i < n; i++)
        buckets[ (a[i]/exp)%10 ]++;

    // 更改buckets[i]。目的是让更改后的buckets[i]的值,是该数据在output[]中的位置。
    for (i = 1; i < 10; i++)
        buckets[i] += buckets[i - 1];

    // 将数据存储到临时数组output[]中
    for (i = n - 1; i >= 0; i--)
    {
        output[buckets[ (a[i]/exp)%10 ] - 1] = a[i];
        buckets[ (a[i]/exp)%10 ]--;
    }

    // 将排序好的数据赋值给a[]
    for (i = 0; i < n; i++)
        a[i] = output[i];
}

/*
 * 基数排序
 *
 * 参数说明:
 *     a -- 数组
 *     n -- 数组长度
 */
void radix_sort(int a[], int n)
{
    int exp;    // 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10;...
    int max = get_max(a, n);    // 数组a中的最大值

    // 从个位开始,对数组a按"指数"进行排序
    for (exp = 1; max/exp > 0; exp *= 10)
        count_sort(a, n, exp);
}

看完了上面的排序,心已是累累滴,给大家推荐一个比较容易理解:通过动画可视化数据结构和算法


猜你喜欢

转载自blog.csdn.net/u010899985/article/details/80980636