【数据结构】八种常见的排序算法

文章目录

  • 1.排序概念及运⽤
    • [1.1 概念](#1.1 概念)
    • [1.2 常⻅排序算法](#1.2 常⻅排序算法)
  • 2.实现常⻅排序算法
    • [2.1 插⼊排序](#2.1 插⼊排序)
      • [2.1.1 直接插⼊排序](#2.1.1 直接插⼊排序)
      • [2.1.2 希尔排序](#2.1.2 希尔排序)
        • [2.1.2.1 希尔排序的时间复杂度计算](#2.1.2.1 希尔排序的时间复杂度计算)
    • [2.2 选择排序](#2.2 选择排序)
      • [2.2.1 直接选择排序](#2.2.1 直接选择排序)
      • [2.2.2 堆排序](#2.2.2 堆排序)
    • [2.3 交换排序](#2.3 交换排序)
      • [2.3.1 冒泡排序](#2.3.1 冒泡排序)
      • [2.3.2 快速排序](#2.3.2 快速排序)
        • [2.3.2.1 hoare版本(重要)](#2.3.2.1 hoare版本(重要))
        • [2.3.2.2 挖坑法](#2.3.2.2 挖坑法)
        • [2.3.2.3 lomuto前后指针(重要)](#2.3.2.3 lomuto前后指针(重要))
        • [2.3.2.4 ⾮递归版本(重要)](#2.3.2.4 ⾮递归版本(重要))
    • [2.4 归并排序 (重要)](#2.4 归并排序 (重要))
    • [2.5 测试代码:排序性能对⽐](#2.5 测试代码:排序性能对⽐)
    • [2.6 ⾮⽐较排序](#2.6 ⾮⽐较排序)
      • [2.6.1 计数排序](#2.6.1 计数排序)
  • 3.排序算法复杂度及稳定性分析

1.排序概念及运⽤

1.1 概念

排序:所谓排序,就是使⼀串记录,按照其中的某个或某些关键字的⼤⼩,递增或递减的排列起来的操作。

1.2 常⻅排序算法

2.实现常⻅排序算法

2.1 插⼊排序

基本思想

直接插⼊排序是⼀种简单的插⼊排序法,其基本思想是:把待排序的记录按其关键码值的⼤⼩逐个插⼊到⼀个已经排好序的有序序列中,直到所有的记录插⼊完为⽌,得到⼀个新的有序序列。

实际中我们玩扑克牌时,就⽤了插⼊排序的思想

2.1.1 直接插⼊排序

当插⼊第 i(i>=1) 个元素时,前⾯的 arr[0],arr[1],...arr[i-1] 已经排好序,此时⽤ arr[i] 的排序码与arr[i-1],arr[i-2]... 的排序码顺序进⾏⽐较,找到插⼊位置,将 array[i] 插⼊,原来位置上的元素顺序后移

代码实现

c 复制代码
void InsertSort(int* arr, int n)  
{ 
    //i表示有序数组的最后一个值
    //arr[n-1]后面没有待排序的数,所以i<n-1
    for (int i = 0; i < n-1; i++) 
    { 
        int end = i ; 
        int tmp = arr[end + 1]; 
        while (end >= 0) 
        { 
            if (arr[end] > tmp)//降序用"<"  
            { 
                arr[end + 1] = arr[end]; 
                end--; 
            } 
            else  
            { 
                break; 
            } 
        } 
        arr[end + 1] = tmp; 
    } 
}
void printArr(int* arr,int n)
{
    for(int i=0;i<n;i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
int main()
{
    int arr[]={5,3,9,6,2,4,7,1,8};
    int size=sizeof(arr)/sizeof(arr[0]);
    InsertSort(arr,size);
    printArr(arr,size);
    return 0;
}

直接插⼊排序的特性总结

  1. 元素集合越接近有序,直接插⼊排序算法的时间效率越⾼
  2. 时间复杂度:O(n2) 【最差情况:1+2+......+n-1,最好情况(O(n)):1+1+......+1】
  3. 空间复杂度:O(1)

2.1.2 希尔排序

希尔排序法⼜称缩⼩增量法。希尔排序法的基本思想是:先选定⼀个整数(通常是gap=n/3+1),把待排序⽂件所有记录分成各组,所有的距离相等的记录分在同⼀组内,并对每⼀组内的记录进⾏排序,然后gap=gap/3+1得到下⼀个整数,再将数组分成各组,进⾏插⼊排序,当gap=1时,就相当于直接插⼊排序。(gap为n,就划分为n组)

它是在直接插⼊排序算法的基础上进⾏改进⽽来的,综合来说它的效率肯定是要⾼于直接插⼊排序算法的。

希尔排序的特性

1.希尔排序是对直接插⼊排序的优化。

  1. 当 gap > 1 时都是预排序,⽬的是让数组更接近于有序。当 gap == 1 时,数组已经接近有序的了,这样就会很快。这样整体⽽⾔,可以达到优化的效果。

代码实现:

c 复制代码
void ShellSort(int* arr, int n)  
{ 
    int gap = n; 
    while (gap > 1) 
    { 
        gap = gap / 3 + 1;//+1是因为当gap=1时是直接插入排序  
        for (int i = 0; i < n - gap; i++) 
        { 
            int end = i; 
            int tmp = arr[end + gap]; 
            while (end >= 0) 
            { 
                if (arr[end] > tmp)  
                { 
                    arr[end + gap] = arr[end]; 
                    end -= gap; 
                } 
                else  
                { 
                    break; 
                } 
            } 
            arr[end + gap] = tmp; 
        } 
    }  
}
2.1.2.1 希尔排序的时间复杂度计算

希尔排序的时间复杂度估算:

外层循环: 外层循环的时间复杂度可以直接给出为:O(logn) 或者O(log3n) ,即O(logn)

内层循环:


gap取值有(以除3为例):n/3,n/9,n/27,... 2,1

• 当gap为n/3时,移动总数为: n/3∗(1 + 2) = n

• 当gap为n/9时,移动总数为: n/9*(1 + 2 + 3 + ... + 8) = 4n

• 最后⼀趟,gap=1即直接插⼊排序,内层循环排序消耗为n

通过以上的分析,可以画出这样的曲线图:

因此,希尔排序在最初和最后的排序的次数都为n,即前⼀阶段排序次数是逐渐上升的状态,当到达某⼀顶点时,排序次数逐渐下降⾄n,⽽该顶点的计算暂时⽆法给出具体的计算过程

希尔排序时间复杂度不好计算,因为 gap 的取值很多,导致很难去计算,因此很多书中给出的希尔排序的时间复杂度都不固定。

《数据结构(C语⾔版)》---严蔚敏书中给出的时间复杂度为:

2.2 选择排序

选择排序的基本思想: 每⼀次从待排序的数据元素中选出最⼩(或最⼤)的⼀个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。

2.2.1 直接选择排序

代码实现:

c 复制代码
//优化后
void SelectSort(int* arr, int n)  
{ 
    int begin = 0, end = n - 1; 
    while (begin < end) 
    { 
        int mini = begin, 
        maxi = begin; 
        for (int i = begin+1; i <= end; i++) 
        { 
            if (arr[i] > arr[maxi])  
            { 
                maxi = i; 
            } 
            if (arr[i] < arr[mini])  
            { 
                mini = i; 
            } 
        } 
        if (begin == maxi)  
        { 
            maxi = mini; 
        } 
        Swap(&arr[mini], &arr[begin]); 
        Swap(&arr[maxi], &arr[end]);  
        ++begin; 
        --end; 
    } 
}

直接选择排序的特性总结: 1. 直接选择排序思考⾮常好理解,但是效率不是很好,实际中很少使⽤

  1. 时间复杂度:O(N2)

  2. 空间复杂度:O(1)

2.2.2 堆排序

堆排序(Heapsort)是指利⽤堆积树(堆)这种数据结构所设计的⼀种排序算法,它是选择排序的⼀种。它是通过堆来进⾏选择数据。需要注意的是排升序要建⼤堆,排降序建⼩堆。

2.3 交换排序

交换排序基本思想: 所谓交换,就是根据序列中两个记录键值的⽐较结果来对换这两个记录在序列中的位置

交换排序的特点是:将键值较⼤的记录向序列的尾部移动,键值较⼩的记录向序列的前部移动

2.3.1 冒泡排序

前⾯在算法题中我们已经接触过冒泡排序的思路了,冒泡排序是⼀种最基础的交换排序。之所以叫做冒泡排序,因为每⼀个元素都可以像⼩⽓泡⼀样,根据⾃⾝⼤⼩⼀点⼀点向数组的⼀侧移动。

代码实现:

c 复制代码
void BubbleSort(int* arr, int n)  
{ 
    int exchange = 0; 
    for (int i = 0; i < n-1; i++) 
    { 
        for (int j = 0; j<n-1-i ; j++) 
        { 
            if (arr[j] > arr[j + 1])  
            { 
                exchange = 1; 
                Swap(&arr[j], &arr[j + 1]); 
            } 
        } 
        if (exchange == 0)  
        { 
            break; 
        } 
    } 
}

冒泡排序的特性总结

• 时间复杂度:O(N2)

• 空间复杂度:O(1)

2.3.2 快速排序

快速排序是Hoare于1962年提出的⼀种⼆叉树结构的交换排序⽅法,其基本思想为:任取待排序元素序列中的某元素作为基准值,按照该排序码将待排序集合分割成两⼦序列,左⼦序列中所有元素均⼩于基准值,右⼦序列中所有元素均⼤于基准值,然后最左右⼦序列重复该过程,直到所有元素都排列在相应位置上为⽌。

快速排序实现主框架:

c 复制代码
//快速排序  
void QuickSort(int* arr, int left, int right)  
{ 
    if (left >= right) 
    { 
        return; 
    } 
    //找基准值,并把基准值放到指定的位置上 
    
    QuickSort(arr, left, meet - 1); 
    QuickSort(arr, meet + 1, right); 
}

如何找基准值,并把基准值放到指定的位置上?

主要有以下⼏种实现⽅式:

2.3.2.1 hoare版本(重要)

算法思路: 1)创建左右指针,确定基准值

2)right:从右向左找出⽐基准值⼩的数据,left:从左向右找出⽐基准值⼤的数据,左右指针数据交换,进⼊下次循环

问题1:为什么跳出循环后right位置的值⼀定不⼤于key? 当 left > right 时,即right⾛到left的左侧,⽽left扫描过的数据均不⼤于key,因此right此时指向的数据⼀定不⼤于key

问题2:为什么left和right指定的数据和key值相等时也要交换? 相等的值参与交换确实有⼀些额外消耗。但是实际还有各种复杂的场景,假设数组中的数据⼤量重复时, ⽆法进⾏有效的分割排序。比如下面这种情况

代码实现:

tips:递归的时间复杂度=单次递归的时间* 循环次数=nlogn

循环次数是二叉树的高度,满二叉树的结点个数n=2k-1,则k=log2n+1

c 复制代码
// 找基准值,时间复杂度不是n^2,而是n,因为两层while循环只循环了一遍数组
int _QuickSort(int* arr, int left, int right)
{
	int keyi = left;
	left ++;
	while (left <= right)
	{
		//right:从右往左找小于基准值的
		while (left <= right&&arr[right] > arr[keyi])
		{
			--right;
		}
		//left:从左往右找大于基准值的
		while (left <= right&&arr[left] < arr[keyi])
		{
			++left;
		}
		//这里找到left比基准值大,right比基准值小
		if(left <= right)
			Swap(&arr[left++], &arr[right--]);
	}
	//right的位置就是基准值的位置
	Swap(&arr[keyi], &arr[right]);
	return right;
}
//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//找基准值keyi
	int keyi = _QuickSort(arr, left, right);
	//左右序列递归继续排序
	QuickSort(arr, left, keyi - 1);
	QuickSort(arr, keyi + 1, right);
}

如果基准值找的不好/数组有序,时间复杂度为n^2。

2.3.2.2 挖坑法

思路: 创建左右指针。⾸先从右向左找出⽐基准⼩的数据,找到后⽴即放⼊左边坑中,当前位置变为新的"坑",然后从左向右找出⽐基准⼤的数据,找到后⽴即放⼊右边坑中,当前位置变为新的"坑",结 束循环后将最开始存储的分界值放⼊当前的"坑"中,返回当前"坑"下标(即分界值下标)

c 复制代码
int _QuickSort(int* arr, int left, int right)  
{ 
    int hole = left; 
    int key = arr[hole]; 
    while (left < right) 
    { 
        while (left < right && arr[right] > key)  
        { 
            --right; 
        } 
        arr[hole] = arr[right]; 
        hole = right; 
        while (left < right && arr[left] < key) 
        { 
            ++left; 
        } 
        arr[hole] = arr[left]; 
        hole = left; 
    } 
    arr[hole] = key; 
    return hole; 
}
2.3.2.3 lomuto前后指针(重要)

创建前后指针,从左往右找⽐基准值⼩的进⾏交换,使得⼩的都排在基准值的左边。

c 复制代码
int _QuickSort(int* arr, int left, int right)  
{ 
    int prev = left, cur = left + 1; 
    int key = left; 
    while (cur <= right) 
    { 
        if (arr[cur] < arr[key] && ++prev != cur)  
        { 
            Swap(&arr[cur], &arr[prev]); 
        } 
        ++cur; 
    } 
    Swap(&arr[key], &arr[prev]);  
    return prev; 
}

快速排序特性总结: 1. 时间复杂度:O(nlogn) 2. 空间复杂度:O(logn)

2.3.2.4 ⾮递归版本(重要)

⾮递归版本的快速排序需要借助数据结构:栈

代码实现

c 复制代码
void QuickSortNonR(int* arr, int left, int right) 
{ 
    ST st; 
    STInit(&st); 
    STPush(&st, right); 
    STPush(&st, left); 
    while (!STEmpty(&st)) 
    { 
        int begin = STTop(&st); 
        STPop(&st); 
        int end = STTop(&st); 
        STPop(&st); 
        // 单趟  
        int keyi = begin; 
        int prev = begin; 
        int cur = begin + 1; 
        while (cur <= end) 
        { 
            if (arr[cur] < arr[keyi] && ++prev != cur) 
                Swap(&arr[prev], &arr[cur]); 
            ++cur; 
        } 
        Swap(&arr[keyi], &arr[prev]); 
        keyi = prev; // [begin, keyi-1] keyi [keyi+1, end]  
        if (keyi + 1 < end) 
        { 
            STPush(&st, end); 
            STPush(&st, keyi + 1); 
        } 
        if (begin < keyi-1) 
        { 
            STPush(&st, keyi-1); 
            STPush(&st, begin); 
        } 
    } 
    STDestroy(&st); 
}

2.4 归并排序 (重要)

归并排序算法思想: 归并排序(MERGE-SORT)是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法(Divide andConquer)的⼀个⾮常典型的应⽤。将已有序的⼦序列合并,得到完全有序的序列;即先使每个⼦序列有序,再使⼦序列段间有序。若将两个有序表合并成⼀个有序表,称为⼆路归并。

归并递归排序核⼼步骤:

代码实现

c 复制代码
void _MergeSort(int* arr, int left, int right, int* tmp)  
{ 
    if (left >= right)  
    { 
        return; 
    } 
    int mid = (right + left) / 2; 
    //[left,mid] [mid+1,right] 
    _MergeSort(arr, left, mid, tmp); 
    _MergeSort(arr, mid + 1, right, tmp);  
    int begin1 = left, end1 = mid; 
    int begin2 = mid + 1, end2 = right; 
    int index = begin1;  
    //合并两个有序数组为⼀个数组  
    while (begin1 <= end1 && begin2 <= end2) 
    { 
        if (arr[begin1] < arr[begin2])  
        { 
            tmp[index++] = arr[begin1++]; 
        } 
        else  
        { 
            tmp[index++] = arr[begin2++]; 
        } 
    } 
    while (begin1 <= end1) 
    { 
        tmp[index++] = arr[begin1++]; 
    } 
    while (begin2 <= end2) 
    { 
        tmp[index++] = arr[begin2++]; 
    } 
    for (int i = left; i <= right; i++) 
    { 
        arr[i] = tmp[i]; 
    } 
} 
void MergeSort(int* arr, int n)  
{ 
    int* tmp = new int[n]; 
    _MergeSort(a, 0, n - 1, tmp); 
    delete[] tmp; 
}

归并排序特性总结: 1. 时间复杂度:O(nlogn) 2. 空间复杂度:O(n)

2.5 测试代码:排序性能对⽐

c 复制代码
// 测试排序的性能对⽐
void TestOP()  
{  
    srand(time(0));  
    const int N = 100000;
    int* a1 = (int*)malloc(sizeof(int)*N);  
    int* a2 = (int*)malloc(sizeof(int)*N);  
    int* a3 = (int*)malloc(sizeof(int)*N);  
    int* a4 = (int*)malloc(sizeof(int)*N);  
    int* a5 = (int*)malloc(sizeof(int)*N);  
    int* a6 = (int*)malloc(sizeof(int)*N);  
    int* a7 = (int*)malloc(sizeof(int)*N);
    
    for (int i = 0; i < N; ++i)  
    {  
        a1[i] = rand();  
        a2[i] = a1[i];  
        a3[i] = a1[i];  
        a4[i] = a1[i];  
        a5[i] = a1[i];  
        a6[i] = a1[i]; 
        a7[i] = a1[i];  
    }  
    int begin1 = clock();  
    InsertSort(a1, N);  
    int end1 = clock();
    
    int begin2 = clock();  
    ShellSort(a2, N);  
    int end2 = clock();
    
    int begin3 = clock();  
    SelectSort(a3, N);  
    int end3 = clock();
    
    int begin4 = clock();  
    HeapSort(a4, N);  
    int end4 = clock();
    
    int begin5 = clock();  
    QuickSort(a5, 0, N-1);  
    int end5 = clock();
    
    int begin6 = clock();  
    MergeSort(a6, N);  
    int end6 = clock();
    
    int begin7 = clock();  
    BubbleSort(a7, N);  
    int end7 = clock();
    
    printf("InsertSort:%d\n", end1 - begin1);  
    printf("ShellSort:%d\n", end2 - begin2);  
    printf("SelectSort:%d\n", end3 - begin3);  
    printf("HeapSort:%d\n", end4 - begin4);  
    printf("QuickSort:%d\n", end5 - begin5);  
    printf("MergeSort:%d\n", end6 - begin6);  
    printf("BubbleSort:%d\n", end7 - begin7);
    
    free(a1);  
    free(a2);  
    free(a3);  
    free(a4);  
    free(a5);  
    free(a6);  
    free(a7);  
}

2.6 ⾮⽐较排序

2.6.1 计数排序

计数排序⼜称为鸽巢原理,是对哈希直接定址法的变形应⽤。

操作步骤: 1)统计相同元素出现次数

2)根据统计的结果将序列回收到原来的序列中代码实现


c 复制代码
void CountSort(int* arr, int n)
{
    //找min,max
	int min = arr[0], max = arr[0];
	for (int i = 1; i < n; i++)
	{
		if (arr[i] > max)
			max = arr[i];
		if (arr[i] < min)
			min = arr[i];
	}
    //确定数组大小range
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
    //对count初始化----calloc,memset
	memset(count, 0, sizeof(int) * range);
	// 统计次数  
    //通过映射的方式将数组保存在count数组中
    //映射的方式==原数组的值-min
	for (int i = 0; i < n; i++)
	{
		count[arr[i] - min]++;
	}
	//将count数组中的数据还原到arr数组中 
	int index = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}
}

计数排序的特性: 计数排序在数据范围集中时,效率很⾼,但是适⽤范围及场景有限。

时间复杂度:O(n + range)

空间复杂度:O(range)

稳定性:稳定

3.排序算法复杂度及稳定性分析

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

四种不稳定排序举例


本节全部代码汇总

c 复制代码
//Sort.h
#include"Sort.h"
#include"Stack.h"
//直接插入排序
void InsertSort(int* arr, int n)
{
    //i表示有序数组的最后一个位置
    //arr[n-1]后面没有待排序的数,所以i<n-1
    for (int i = 0; i < n - 1; i++)
    {
        int end = i;
        int tmp = arr[end + 1];
        while (end >= 0)
        {
            if (arr[end] > tmp)//降序用"<"  
            {
                arr[end + 1] = arr[end];
                end--;
            }
            else
            {
                break;
            }
        }
        arr[end + 1] = tmp;
    }
}

//希尔排序
void ShellSort(int* arr, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;
		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = arr[end + gap];
			while (end >= 0)
			{
				if (arr[end] > tmp)
				{
					arr[end + gap] = arr[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			arr[end + gap] = tmp;
		}
	}
}

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

//向下调整算法 logn
void AdjustDown(int* arr, int parent, int n)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		//建大堆:<
		//建小堆: >
		if (child + 1 < n && arr[child] < arr[child + 1])
		{
			child++;
		}
		//孩子和父亲比较
		//建大堆:>
		//建小堆:<
		if (arr[child] > arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else {
			break;
		}
	}
}

//堆排序------------使用的是堆结构的思想 n * logn
void HeapSort(int* arr, int n)
{
	//向下调整算法------建堆n
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, i, n);//调整堆,使其满足堆的性质
	}

	////向上调整算法建堆n*logn
	//for (int i = 0; i < n; i++)
	//{
	//	AdjustUp(arr, i);
	//}

	//n*logn
	int end = n - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);
		AdjustDown(arr, 0, end);//logn
		end--;
	}
}


//1)直接选择排序
//void SelectSort(int* arr, int n)
//{
//	for (int i = 0; i < n-1; i++)
//	{
//		int min = i;
//		for (int j = i+1; j < n; j++)
//		{
//			if (arr[j] < arr[min])
//				min = j;
//		}
//		Swap(&arr[min], &arr[i]);
//	}
//}
void SelectSort(int* arr, int n)
{
	int begin = 0, end = n - 1;
	while (begin < end)
	{
		int mini = begin;
		int	maxi = begin;
		//比较每个数
		for (int i = begin+1; i <= end; i++)
		{
			if (arr[i] > arr[maxi])
			{
				maxi = i;
			}
			if (arr[i] < arr[mini])
			{
				mini = i;
			}
		}
		//当开头就是最大值时,最小值会和开头交换,&arr[maxi]此时存的是最小值
		if (begin == maxi)
		{
			maxi = mini;
		}
		Swap(&arr[mini], &arr[begin]);
		Swap(&arr[maxi], &arr[end]);
		++begin;
		--end;
	}
}

//冒泡排序 n^2
void BubbleSort(int* arr, int n)
{
	int exchange = 0;
	for (int i = 0; i < n-1; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			if(arr[j]>arr[j+1])
			{
				exchange = 1;
				Swap(&arr[j], &arr[j + 1]);
			}
		}
		if (exchange == 0)
			break;
	}
}

//找基准值hoare
int _QuickSort1(int* arr, int left, int right)
{
	int keyi = left;
	left++;
	while (left <= right)
	{
		//right:从右往左找小于基准值的
		while (left <= right && arr[right] > arr[keyi])
		{
			--right;
		}
		//left:从左往右找大于基准值的
		while (left <= right && arr[left] < arr[keyi])
		{
			++left;
		}
		//这里找到left比基准值大,right比基准值小
		if (left <= right)
			Swap(&arr[left++], &arr[right--]);
	}
	//right的位置就是基准值的位置
	Swap(&arr[keyi], &arr[right]);
	return right;
}
//挖坑法
int _QuickSort2(int* arr, int left, int right)
{
	int hole = left;
	int key = arr[hole];
	while (left < right)
	{
		while (left < right && arr[right] > key)
		{
			--right;
		}
		arr[hole] = arr[right];
		hole = right;
		while (left < right && arr[left] < key)
		{
			++left;
		}
		arr[hole] = arr[left];
		hole = left;
	}
	arr[hole] = key;
	return hole;
}
//lomuto
int _QuickSort(int* arr, int left, int right)
{
	int prev = left, cur = left + 1;
	int key = left;
	while (cur <= right)
	{
		if (arr[cur] < arr[key] && ++prev != cur)
		{
			Swap(&arr[cur], &arr[prev]);
		}
		++cur;
	}
	Swap(&arr[key], &arr[prev]);
	return prev;
}

//快速排序
void QuickSort(int* arr, int left, int right)
{
	if (left >= right)
	{
		return;
	}
	//找基准值keyi
	int keyi = _QuickSort(arr, left, right);
	//左右序列递归继续排序
	QuickSort(arr, left, keyi - 1);
	QuickSort(arr, keyi + 1, right);
}

//非递归快速排序
void QuickSortNonR(int* arr, int left, int right)
{
	ST st;
	STInit(&st);
	STPush(&st, right);
	STPush(&st, left);
	while (!STEmpty(&st))
	{
		int begin = STTop(&st);
		STPop(&st);
		int end = STTop(&st);
		STPop(&st);
		//[begin,end]---在这个范围里找基准值
		// 使用lomuto法
		int keyi = begin;
		int prev = begin;
		int cur = begin + 1;
		while (cur <= end)
		{
			if (arr[cur] < arr[keyi] && ++prev != cur)
				Swap(&arr[prev], &arr[cur]);
			++cur;
		}
		Swap(&arr[keyi], &arr[prev]);
		keyi = prev; //此时keyi又成为了基准值的位置
		//左序列[begin, keyi-1] 
		//右序列[keyi+1, end]  
		if (keyi + 1 < end)
		{
			STPush(&st, end);
			STPush(&st, keyi + 1);
		}
		if (begin < keyi - 1)
		{
			STPush(&st, keyi - 1);
			STPush(&st, begin);
		}
	}
	STDestroy(&st);
}

//归并排序
void _MergeSort(int* arr, int left, int right, int* tmp)
{
	//分解
	if (left >= right)//虽然没有>的可能,但是这样代码健壮性更好,只写==也可以
	{
		return;
	}
	int mid = (right + left) / 2;
	//左序列:[left,mid] 
	//右序列:[mid+1,right]
	// 左序列继续二分
	_MergeSort(arr, left, mid, tmp);
	//右序列二分
	_MergeSort(arr, mid + 1, right, tmp);

	//合并两个有序数组为⼀个数组  
	int begin1 = left, end1 = mid;
	int begin2 = mid + 1, end2 = right;
	int index = begin1;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (arr[begin1] < arr[begin2])
		{
			tmp[index++] = arr[begin1++];
		}
		else
		{
			tmp[index++] = arr[begin2++];
		}
	}
	while (begin1 <= end1)
	{
		tmp[index++] = arr[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[index++] = arr[begin2++];
	}
	//拷贝回arr
	for (int i = left; i <= right; i++)
	{
		arr[i] = tmp[i];
	}
}
void MergeSort(int* arr, int n)
{
	int* tmp = (int*)malloc(n*sizeof(int));
	//[0,n-1]
	//二分
	_MergeSort(arr, 0, n - 1, tmp);
	free(tmp);
}

//非比较排序
void CountSort(int* arr, int n)
{
	//找min,max
	int min = arr[0], max = arr[0];
	for (int i = 1; i < n; i++)
	{
		if (arr[i] > max)
			max = arr[i];
		if (arr[i] < min)
			min = arr[i];
	}

	//确定数组大小range
	int range = max - min + 1;
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	//对count初始化----calloc,memset
	memset(count, 0, sizeof(int) * range);
	// 统计次数  
	//通过映射的方式将数组保存在count数组中
	//映射的方式==原数组的值-min
	for (int i = 0; i < n; i++)
	{
		count[arr[i] - min]++;
	}
	//将count数组中的数据还原到arr数组中 
	int index = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{
			arr[index++] = i + min;
		}
	}
}
c 复制代码
//test.c
#include"Sort.h"

void  printArr(int* arr, int n)
{
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

void test01()
{
	//int a[] = { 5, 3, 9, 6, 2, 4, 7, 1, 8 };
	//int a[] = { 5, 3, 9, 6, 2, 4 };
	int a[] = { 6,1,2,7,9,3 };
	int n = sizeof(a) / sizeof(a[0]);
	printf("排序之前:");
	printArr(a, n);

	//InsertSort(a,n);
	//ShellSort(a, n);
	//SelectSort(a, n);
	//QuickSort(a, 0, n - 1);
	//QuickSortNonR(a, 0, n - 1);
	//MergeSort(a, n);
	CountSort(a, n);

	printf("排序之后:");
	printArr(a, n);
}

// 测试排序的性能对⽐
void TestOP()
{
	srand(time(0));
	const int N = 100000;
	int* a1 = (int*)malloc(sizeof(int) * N);
	int* a2 = (int*)malloc(sizeof(int) * N);
	int* a3 = (int*)malloc(sizeof(int) * N);
	int* a4 = (int*)malloc(sizeof(int) * N);
	int* a5 = (int*)malloc(sizeof(int) * N);
	int* a6 = (int*)malloc(sizeof(int) * N);
	int* a7 = (int*)malloc(sizeof(int) * N);
	for (int i = 0; i < N; ++i)
	{
		a1[i] = rand();
		a2[i] = a1[i];
		a3[i] = a1[i];
		a4[i] = a1[i];
		a5[i] = a1[i];
		a6[i] = a1[i];
		a7[i] = a1[i];
	}
	int begin1 = clock();
	InsertSort(a1, N);
	int end1 = clock();

	int begin2 = clock();
	ShellSort(a2, N);
	int end2 = clock();

	int begin3 = clock();
	SelectSort(a3, N);
	int end3 = clock();

	int begin4 = clock();
	HeapSort(a4, N);
	int end4 = clock();

	int begin5 = clock();
	QuickSort(a5, 0, N - 1);
	int end5 = clock();

	int begin6 = clock();
	MergeSort(a6, N);
	int end6 = clock();

	int begin7 = clock();
	BubbleSort(a7, N);
	int end7 = clock();

	printf("InsertSort:%d\n", end1 - begin1);
	printf("ShellSort:%d\n", end2 - begin2);
	printf("SelectSort:%d\n", end3 - begin3);
	printf("HeapSort:%d\n", end4 - begin4);
	printf("QuickSort:%d\n", end5 - begin5);
	printf("MergeSort:%d\n", end6 - begin6);
	printf("BubbleSort:%d\n", end7 - begin7);

	free(a1);
	free(a2);
	free(a3);
	free(a4);
	free(a5);
	free(a6);
	free(a7);
}

int main()
{
	test01();
	//TestOP();
	return 0;
}
c 复制代码
//Sort.h
#pragma once
#include<stdio.h>
#include<memory.h>
#include<stdlib.h>

//插入排序
//1)直接插入排序 <n^2
void InsertSort(int* arr, int n);
//2)希尔排序 n^1.3
void ShellSort(int* arr, int n);

//选择排序
//1)直接选择排序 n^2
void SelectSort(int* arr, int n);
//2)堆排序 nlogn
void HeapSort(int* arr, int n);

//交换排序
//1)冒泡排序 n^2
void BubbleSort(int* arr, int n);
//2)快速排序 nlogn
void QuickSort(int* arr, int left, int right);
//3)非递归版本的快速排序
void QuickSortNonR(int* arr, int left, int right);

//归并排序 nlogn
void MergeSort(int* arr, int n);

//非比较排序
void CountSort(int* arr, int n);
c 复制代码
//Stack.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdbool.h>
#include<stdlib.h>
typedef int STDataType;
typedef struct Stack
{
    STDataType* arr;//数组 
    int top;//有效数据的个数,也是栈顶 
    int capacity;//栈的空间大小
}ST;
// 初始化栈 
void STInit(ST* ps);
// 销毁栈 
void STDestroy(ST* ps);
// ⼊栈 
void STPush(ST* ps, STDataType x);
//出栈 
void STPop(ST* ps);
//取栈顶元素 
STDataType STTop(ST* ps);
//获取栈中有效元素个数 
int STSize(ST* ps);
//栈是否为空 
bool STEmpty(ST* ps);
c 复制代码
//Stack.c
#include"Stack.h"
// 初始化栈 
void STInit(ST* ps)
{
	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}
// 销毁栈 
void STDestroy(ST* ps)
{
	if (ps->arr)
		free(ps->arr);
	ps->arr = NULL;
	ps->top = ps->capacity = 0;
}
// ⼊栈 
void STPush(ST* ps, STDataType x)
{
	assert(ps);
	//先判断空间是否足够
	if (ps->top == ps->capacity)
	{
		//如果capacity==0;不能直接*2
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		STDataType* tmp = (STDataType*)realloc(ps->arr, newCapacity * sizeof(STDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			exit(1);
		}
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
	//空间足够
	ps->arr[ps->top++] = x;
}
//出栈 
void STPop(ST* ps)
{
	assert(!STEmpty(ps));
	ps->top--;
}
//取栈顶元素 
STDataType STTop(ST* ps)
{
	assert(!STEmpty(ps));
	return ps->arr[ps->top - 1];
}
//获取栈中有效元素个数 
int STSize(ST* ps)
{
	assert(ps);//传的参数指针不为空就行,有效数据个数可以为0
	return ps->top;
}
//栈是否为空 
bool STEmpty(ST* ps)
{
	assert(ps);
	return ps->top == 0;//如果top==0就直接返回
}
相关推荐
寻寻觅觅☆12 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
偷吃的耗子12 小时前
【CNN算法理解】:三、AlexNet 训练模块(附代码)
深度学习·算法·cnn
2013编程爱好者13 小时前
【C++】树的基础
数据结构·二叉树··二叉树的遍历
NEXT0613 小时前
二叉搜索树(BST)
前端·数据结构·面试
化学在逃硬闯CS13 小时前
Leetcode1382. 将二叉搜索树变平衡
数据结构·算法
ceclar12313 小时前
C++使用format
开发语言·c++·算法
Gofarlic_OMS14 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
夏鹏今天学习了吗14 小时前
【LeetCode热题100(100/100)】数据流的中位数
算法·leetcode·职场和发展
忙什么果15 小时前
上位机、下位机、FPGA、算法放在哪层合适?
算法·fpga开发
董董灿是个攻城狮15 小时前
AI 视觉连载4:YUV 的图像表示
算法