七种常见的排序算法和实现

2024-08-22 11:12

本文主要是介绍七种常见的排序算法和实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.排序的概念及其运用

1.1排序的概念

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

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

内部排序:数据元素全部放在内存中的排序。

外部排序:数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

1.2 常见的排序算法

 我们依次对下面排序进行讲解,并用下面函数来测试其性能。

// 排序实现的接口
// 插入排序
void InsertSort(int* a, int n);// 希尔排序void ShellSort(int* a, int n);// 选择排序
void SelectSort(int* a, int n);// 堆排序
void AdjustDwon(int* a, int n, int root);void HeapSort(int* a, int n);// 冒泡排序
void BubbleSort(int* a, int n)// 快速排序递归实现
// 快速排序hoare版本
int PartSort1(int* a, int left, int right);// 快速排序挖坑法
int PartSort2(int* a, int left, int right);// 快速排序前后指针法
int PartSort3(int* a, int left, int right);void QuickSort(int* a, int left, int right);// 快速排序 非递归实现
void QuickSortNonR(int* a, int left, int right)// 归并排序递归实现
void MergeSort(int* a, int n)// 归并排序非递归实现
void MergeSortNonR(int* a, int n)// 计数排序
void CountSort(int* a, int 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);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];}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();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);free(a1);free(a2);free(a3);free(a4);free(a5);free(a6);
}

2.常见排序算法的实现

2.1 插入排序
2.1.1基本思想:

OJ链接 直接插入排序是一种简单的插入排序法,其基本思想是:

比特科技 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中,直到所有的记录插入完为 止,得到一个新的有序序列。

2.1.2直接插入排序:

将后面数依次和前面数相比较,当碰到小于它的数时就插在其身后。

void InsertSort(int* a, int n)
{for (int i = 0; i < n - 1; i++){int end = i;int tmp = a[end + 1];while (end >= 0){if (tmp < a[end]){a[end + 1] = a[end];end--;}elsebreak;}a[end + 1] = tmp;}	
}

直接插入排序的特性总结:

1. 元素集合越接近有序,直接插入排序算法的时间效率越高

2. 时间复杂度:O(N^2)

3. 空间复杂度:O(1),它是一种稳定的排序算法

4. 稳定性:稳定

2.1.3 希尔排序( 缩小增量排序 )

希尔排序法又称缩小增量法。希尔排序法的基本思想是:先选定一个整数,把待排序文件中所有记录分成个组,所有距离为的记录分在同一组内,并对每一组内的记录进行排序。然后,取重复上述分组和排序的工 作。当到达=1时,所有记录在统一组内排好序。

void ShellSort(int* a, int n)
{int gam = n-1;while (gam >= 1){gam /= 2;for (int i = 0; i < n-gam; i++){int end = i;int tmp = a[end + gam];while (end >= 0){if (tmp < a[end]){a[end + gam] = a[end];end -= gam;}elsebreak;}a[end + gam] = tmp;}}
}

希尔排序的特性总结:

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

2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就 会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。

3. 希尔排序的时间复杂度不好计算,因为gap的取值方法很多,导致很难去计算,因此在好些树中给出的 希尔排序的时间复杂度都不固定: 

4 . 稳定性:不稳定。

2.2 选择排序
2.2.1基本思想:

每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的 数据元素排完 。

2.2.2 直接选择排序:

在元素集合array[i]--array[n-1]中选择关键码最大(小)的数据元素。

若它不是这组元素中的最后一个(第一个)元素,则将它与这组元素中的最后一个(第一个)元素交换。

在剩余的array[i]--array[n-2](array[i+1]--array[n-1])集合中,重复上述步骤,直到集合剩余1个元素。

 

注意:作者每次都去选择最大和最小数 

void SelectSort(int* a, int n)
{int left=0, right=n-1;int mini = left, maxi = left;while (left < right){for (int i = left+1; i <= right; i++){if (a[i] < a[mini])mini = i;if (a[i]> a[maxi])maxi = i;}Swap(&a[left], &a[mini]);if (left == maxi)maxi = mini;Swap(&a[right], &a[maxi]);left++;right--;}
}

 直接选择排序的特性总结:

1. 直接选择排序思考非常好理解,但是效率不是很好。实际中很少使用

2. 时间复杂度:O(N^2)

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

4. 稳定性:不稳定

2.2.3 堆排序

堆排序(Heapsort)是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。它是 通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。

void  AdjustDwon(int* a, int n, int parent)
{int child = parent * 2 + 1;while (child < n){if (child + 1 < n && a[child] < a[child + 1]){child++;}if (a[parent] < a[child]){Swap(&a[parent], &a[child]);parent = child;child = parent * 2 + 1;}elsebreak;}
}
void AdjustUp(int* a, int child)
{int parent = (child - 1) / 2;while (child > 0){if (a[child] > a[parent]){//交换Swap(&a[child], &a[parent]);child = parent;parent = (child - 1) / 2;}elsebreak;}}
void HeapSort(int* a, int n)
{for (int i = 0; i < n; i++){AdjustUp(a, i);}int end = n - 1;while (end > 0){Swap(&a[0], &a[end]);AdjustDwon(a, end, 0);end--;}
}

 直接选择排序的特性总结:

1. 堆排序使用堆来选数,效率就高了很多。

2. 时间复杂度:O(N*logN)

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

4. 稳定性:不稳定

2.3 交换排序

根据两个数比较的结果来进行交换,从而排出有序。

2.3.1冒泡排序

每次排序会将最大数或者最小数排好。

void BubbleSort(int* a, int n)
{for (int j = 0; j < n; j++){int tmp = 1;for (int i = 1; i < n - j; i++){if (a[i - 1] > a[i]){Swap(&a[i - 1], &a[i]);tmp = 0;}			}if (tmp == 1)break;}	
}

冒泡排序的特性总结:

1. 冒泡排序是一种非常容易理解的排序

2. 时间复杂度:O(N^2)

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

4. 稳定性:稳定 

2.3.2 快速排序

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

1. hoare版本

int GetmidNumi(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[mid] >= a[right]&& a[mid] <= a[left]|| a[mid] <= a[right] && a[mid] >= a[left])return mid;if (a[right] >= a[mid] && a[right] <= a[left] || a[right] <= a[mid] && a[right] >= a[left])return right;if (a[left] >= a[mid] && a[left] <= a[right] || a[left] <= a[mid] && a[left] >= a[right])return left;
}
void PartSort1(int* a, int left, int right)
{if (left >= right)return;//随机key/*int randi = left+(rand() % (right - left));Swap(&a[randi], &a[left]);*///三数选中Swap(&a[GetmidNumi(a, left, right)],&a[left]);int keyi=left;int L = left, R = right;while (L < R){while (L<R && a[R]>=a[keyi])R--;while (L<R && a[L]<=a[keyi])L++;Swap(&a[L], &a[R]);}Swap(&a[R], &a[keyi]);keyi = R;PartSort1(a, left, keyi-1);PartSort1(a, keyi + 1, right);
}

 2. 挖坑法

int GetmidNumi(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[mid] >= a[right]&& a[mid] <= a[left]|| a[mid] <= a[right] && a[mid] >= a[left])return mid;if (a[right] >= a[mid] && a[right] <= a[left] || a[right] <= a[mid] && a[right] >= a[left])return right;if (a[left] >= a[mid] && a[left] <= a[right] || a[left] <= a[mid] && a[left] >= a[right])return left;
}
void PartSort2(int* a, int left, int right)
{if (left >= right)return;//随机key/*int randi = left+(rand() % (right - left));Swap(&a[randi], &a[left]);*///三数选中Swap(&a[GetmidNumi(a, left, right)], &a[left]);int key = a[left];int L = left, R = right;while (L < R){while (L < R && a[R] >=key)R--;a[L] = a[R];while (L < R && a[L] <= key)L++;a[R] = a[L];}Swap(&a[R], &key);PartSort2(a, left, R - 1);PartSort2(a, R + 1, right);
}

3. 前后指针版本

int GetmidNumi(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[mid] >= a[right]&& a[mid] <= a[left]|| a[mid] <= a[right] && a[mid] >= a[left])return mid;if (a[right] >= a[mid] && a[right] <= a[left] || a[right] <= a[mid] && a[right] >= a[left])return right;if (a[left] >= a[mid] && a[left] <= a[right] || a[left] <= a[mid] && a[left] >= a[right])return left;
}
int PartSort3(int* a, int left, int right)
{//随机key/*int randi = left+(rand() % (right - left));Swap(&a[randi], &a[left]);*///三数选中Swap(&a[GetmidNumi(a, left, right)], &a[left]);int keyi = left;int prev = left, cur = left+1;while (cur <= right){if(a[cur]<a[keyi]&&++prev!=cur)Swap(&a[cur], &a[prev]);cur++;}Swap(&a[prev], &a[keyi]);keyi = prev;return keyi;
}
2.3.3 快速排序优化

1.三数取中选key法:在最后和最前和中间选一个第二大的数

int GetmidNumi(int* a, int left, int right)
{int mid = (right + left) / 2;if (a[mid] >= a[right]&& a[mid] <= a[left]|| a[mid] <= a[right] && a[mid] >= a[left])return mid;if (a[right] >= a[mid] && a[right] <= a[left] || a[right] <= a[mid] && a[right] >= a[left])return right;if (a[left] >= a[mid] && a[left] <= a[right] || a[left] <= a[mid] && a[left] >= a[right])return left;
}

2.递归到小的子区间时可以考虑用插入排序(递归到后面,会有大量繁琐的函数递归,要是直接用插入解决会更快)

快速排序的特性总结:

1. 快速排序整体的综合性能和使用场景都是比较好的,所以才敢叫快速排序

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(logN)

4. 稳定性:不稳定

2.4 归并排序

基本思想: 归并排序(MERGE-SORT)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有 序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。 归并排序核心步骤: 

 

void MergeSort(int* a,int begin,int end,int* tmp)
{if (begin >= end)return;int mid = (begin + end) / 2;int begin1 = begin, end1 = mid;int begin2 = mid + 1, end2 = end;int j = begin;MergeSort(a, begin1, end1, tmp);MergeSort(a, begin2, end2, tmp);while (begin1 <= end1 && begin2 <= end2){if (a[begin1] < a[begin2])tmp[j++] = a[begin1++];elsetmp[j++] = a[begin2++];}while (begin1 <= end1 || begin2 <= end2){if (begin1 <= end1)tmp[j++] = a[begin1++];elsetmp[j++] = a[begin2++];}memcpy(a+begin , tmp+begin , sizeof(int) * (end-begin+1));
}

归并排序的特性总结:

1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。

2. 时间复杂度:O(N*logN)

3. 空间复杂度:O(N)

4. 稳定性:稳定 

3.总结

这篇关于七种常见的排序算法和实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1096109

相关文章

C++中unordered_set哈希集合的实现

《C++中unordered_set哈希集合的实现》std::unordered_set是C++标准库中的无序关联容器,基于哈希表实现,具有元素唯一性和无序性特点,本文就来详细的介绍一下unorder... 目录一、概述二、头文件与命名空间三、常用方法与示例1. 构造与析构2. 迭代器与遍历3. 容量相关4

C++中悬垂引用(Dangling Reference) 的实现

《C++中悬垂引用(DanglingReference)的实现》C++中的悬垂引用指引用绑定的对象被销毁后引用仍存在的情况,会导致访问无效内存,下面就来详细的介绍一下产生的原因以及如何避免,感兴趣... 目录悬垂引用的产生原因1. 引用绑定到局部变量,变量超出作用域后销毁2. 引用绑定到动态分配的对象,对象

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

深入理解Mysql OnlineDDL的算法

《深入理解MysqlOnlineDDL的算法》本文主要介绍了讲解MysqlOnlineDDL的算法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小... 目录一、Online DDL 是什么?二、Online DDL 的三种主要算法2.1COPY(复制法)

Linux下利用select实现串口数据读取过程

《Linux下利用select实现串口数据读取过程》文章介绍Linux中使用select、poll或epoll实现串口数据读取,通过I/O多路复用机制在数据到达时触发读取,避免持续轮询,示例代码展示设... 目录示例代码(使用select实现)代码解释总结在 linux 系统里,我们可以借助 select、

Linux挂载linux/Windows共享目录实现方式

《Linux挂载linux/Windows共享目录实现方式》:本文主要介绍Linux挂载linux/Windows共享目录实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录文件共享协议linux环境作为服务端(NFS)在服务器端安装 NFS创建要共享的目录修改 NFS 配

通过React实现页面的无限滚动效果

《通过React实现页面的无限滚动效果》今天我们来聊聊无限滚动这个现代Web开发中不可或缺的技术,无论你是刷微博、逛知乎还是看脚本,无限滚动都已经渗透到我们日常的浏览体验中,那么,如何优雅地实现它呢?... 目录1. 早期的解决方案2. 交叉观察者:IntersectionObserver2.1 Inter