算法:六种比较排序算法
本文是本人读过《算法导论》之后所写,C代码实现是我尽量参照伪代码所写,如有错误,敬请指正。
*:所有排序算法默认从小到大排序,伪代码数组的首元素为A[1], 数组长度为n
一、冒泡排序
冒泡排序应该是最简单的比较排序了,排序原理就是重复遍历数组,每次比较相邻的两个元素,如果前一个元素大于后一个元素,则交换数组两个元素的位置。这样每遍历一次,最大的元素就会下沉到数组最底部,重复遍历n-1次,所有元素就都已排好序了。
伪代码:
1. for i = 1 to n-1
2. for j = 1 to n-i
3. if A[j] > A[j+1]
4. exchange A[j] with A[j+1]
伪代码讲解:
第一行控制遍历轮数;
第二行控制需要比较的数组元素下标范围;
第三四行,当相邻的两个元素不满足比较条件时,交换两个元素的位置
C代码:
![](/assets/blank.gif)
![](/assets/blank.gif)
/*Author:Terry Zhang*/ #include <stdio.h> #include <stdlib.h>int main(void) {size_t n = 0;scanf_s("%d", &n);int *p = (int *)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d", p + i);}int * p0 = p;for (size_t i = 0; i < n-1; i++){for (size_t j = 0; j < n-i-1; j++){int t = 0;if (p[j] > p[j + 1]){t = p[j];p[j] = p[j + 1];p[j + 1] = t;}}}p0 = p;for (size_t i = 0; i < n; i++){printf("%d ", *(p0++));}printf("\n");free(p);return 0; }
View Code
二、选择排序
选择排序是一种简单直观的排序算法,排序思路是,第一次通过比较选出数组中最小的元素放在数组的起始位置,接着比较剩下的元素选出最小的元素放在已经排好序的序列后面,以此类推,直到所有元素排序完毕。
伪代码:
1. for i=1 to n-1
2. min = i;
3. for j=i+1 to n
4. if A[j] < A[min]
5. min = j;
6. if min != i
7. exchange A[i] with A[min]
伪代码讲解:
第一行控制遍历轮数;
第二行将最小值下标设置为当前未排序数组下标的第一个;
第四五行,如果发现比当前最小值小的元素,则将min更新;
第六七行,如果最小值下标和当前未排序数组下标的第一个(j)不等,则交换A[i]和A[min],即将最小值移动到已排序数组末尾
C代码:
![](/assets/blank.gif)
![](/assets/blank.gif)
#include <stdio.h> #include <stdlib.h>int main(void) {size_t n = 0;scanf_s("%d", &n);int *p = (int *)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d", p + i);}for (size_t j = 0; j < n - 1; j++){int min = j;for (size_t k = j + 1; k < n; k++){if (p[k] <= p[min])min = k;}if (min != j){int t = p[j];p[j] = p[min];p[min] = t;}}int * p0 = p;for (size_t i = 0; i < n; i++){printf("%d ", *(p0++));}printf("\n");free(p);return 0; }
View Code
三、插入排序
插入排序的排序思想是,通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
伪代码:
1. for i = 2 to n
2. key = A[i];
3. j = i - 1;
4. while j >= 1 and A[j] > key
5. A[j+1] = A[j];
6. j = j - 1;
7. A[j + 1] = key;
伪代码讲解:
第一行控制循环轮数;
第二行key为当前需要插入的元素;
第四到六行,将比key大的元素依次后移,直到遇到第一个不大于key的元素跳出循环;
第七行将需要插入的元素key插入到在已排序好的序列中应有的位置
C代码:
![](/assets/blank.gif)
![](/assets/blank.gif)
/*Author:Terry Zhang*/ #include<stdio.h> #include<stdlib.h>int main(void) {//输入n个数size_t n = 0;scanf_s("%d",&n);int *p;p = (int *)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d", p + i);}//排序int key = 0, i = 0;for (size_t j = 1; j < n; j++){key = p[j];i = j - 1;while (i >= 0 && p[i] > key){p[i + 1] = p[i];i--;}p[i + 1] = key;}//格式化输出int *p0 = p;for (size_t i = 0; i < n; i++){printf("%d ",*p0++);}printf("\n");free(p);return 0; }
View Code
四、堆排序
堆排序像插入排序而不像归并排序(见第五种排序),它是一种原址排序算法(元素的相对位置排序前后不发生变化);像归并排序而不像插入排序,堆排序运行时间为O(nlgn). (二叉)堆数据结构是一种数据对象,它可以被视为一棵近似的完全二叉树,树中每一个元素分别对应数组中的一个元素,且从左到右依次排列。这样给定一个节点的下标i,我们很容易计算得到它的父节点、左孩子和右孩子的下标。在排序算法中我们使用最大堆(满足A[Parent(i)] >= A[i]),最小堆用于构造优先队列。
Parent(i)
return
Left(i)
return 2i
Right
return 2i+1
下面我们介绍排序算法中需要用到的三个函数
1. ManHeapify过程:时间复杂度为O(lgn), 它是维护最大堆性质的关键
2. BuildMaxHeap过程:它具有线性时间复杂度,功能是从无序的数组中构造一个最大堆
3.HeapSort排序过程:时间复杂度为O(nlgn),功能是对一个数组进行原址排序
维护堆的性质:
我们通过MaxHeapify函数来维护堆的性质,我们假定根节点为Left(i)和Right(i)的二叉树都是最大堆,此时A[i]可能小于它的孩子,此函数的目的就是让A[i]在最大堆中“逐级下降”,从而使得以下标i为根节点的子树重新遵循最大堆的性质。
伪代码:
MaxHeapify(A,i)
1. l = Left(i)
2. r = Right(i)
3. if l <= n and A[l] > A[i]
4. largest = l
5. else largest = i
6. if r <= n and A[r] > A[largest]
7. largest = r
8. if largest != i
9. exchange A[i] with A[largest]
10 MaxHeapify(A,largest)
伪代码解释:
第一二行获取根节点i的左右孩子的下标;
第三行到七行比较根节点与左右孩子的大小,更新largest为三者中最大元素的下标;
第八九行,如果根节点largest!= i,即根节点小于某个孩子,则交换根节点与A[largest]的位置;
第十行,重复1~9
建堆:
我们可以利用自底向上的方法利用过程Maxheapify把一个大小为n的数组转换为最大堆,通过计算我们知道,A[n/2 + 1] to A[n]都是叶结点,而每个叶节点可以看成是只有一个元素的堆。该过程对树中其他结点都调用一次MaxHeapify从范围完成建堆过程
伪代码:
BuildMaxHeap(A)
1. for i = n/2 downto 1
2. MaxHeapify(A,i)
堆排序算法:
我们已经直到最大堆的一个最重要的性质就是根节点永远大于等于子结点,也就是说最大的元素永远在根节点A[1]处,我们可以利用这一性质对数组进行排序。排序的方法是交换A[1]和A[n]的位置,之后去掉结点n,维持堆的性质,之后重复此过程,最终完成排序。
伪代码:
HeapSort(A)
1. BuildMaxHeap(A)
2. for i = A.length downto 2
3. exchange A[1] with A[i]
4. A.heap_size = A.heap_size - 1
5. MaxHeapify(A,1)
完整的C代码如下:
![](/assets/blank.gif)
![](/assets/blank.gif)
/*Authority:Terry Zhang*/ #include <stdio.h> #include <stdlib.h>int main() {size_t n = 0;scanf_s("%d", &n);int *p = (int *)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d", p + i);}void BuildMaxHeap(int *A, int n);void HeapSort(int *A, int n);HeapSort(p, n);int *p0 = p;for (size_t i = 0; i < n; i++){printf("%d ", *p0++);}free(p);return 0; }//maintain max heap function void MaxHeapify(int *A, int n, int i) {int Left(int i);int Right(int i);void Swap(int *A, int i, int j);int l = Left(i); //get the subscript of its left childint r = Right(i); //get the subscript of its right childint largest;if (l < n && A[l] > A[i]) //n = A.heap_sizelargest = l;elselargest = i;if (r < n && A[r] > A[largest]) //n = A.heap_sizelargest = r;if (largest != i){Swap(A, i, largest);MaxHeapify(A, n, largest);} }//build max heap void BuildMaxHeap(int *A, int n) {for (int i = n / 2 - 1; i >= 0; i--){MaxHeapify(A, n, i);} }//heap sort void HeapSort(int *A,int n) {BuildMaxHeap(A, n);void Swap(int *A, int i, int j);for (int i = n - 1; i >= 1; i--) //n -1 to 1 {Swap(A, 0, i);MaxHeapify(A, i, 0);} }//return the subscript of i's left child int Left(int i) {return (i <<= 1) + 1; }//return the subscipts of i's right child int Right(int i) {return (i <<= 1) + 2; }void Swap(int *A, int i, int j) {int temp = A[i];A[i] = A[j];A[j] = temp; }
View Cod
五、归并排序
归并排序是一种使用分治法(divide and conquer)的排序算法,分治法的思想是将原有的一个规模比较大的问题,分解成若干个规模较小但又类似于原问题的子问题,然后求解子问题,再合并子问题的解来建立原问题的解。
分治模式在每层递归中都有三个步骤:
1. 分解原问题为若干子问题,这些子问题都是原问题规模较小的实例
2. 解决这些子问题。递归地求解子问题,当子问题规模足够小时则直接求解
3. 合并这些子问题的解形成原问题的解
归并排序完全符合分治模式
1. 分解:待排序的n个元素的序列成各具n/2个元素的两个子序列
2. 解决:使用归并排序递归的排序两个子序列
3. 合并:合并两个已排好的子序列产生已排序的整个序列
当待排序的序列长度为1时,递归“开始回升”,因为长度为1的序列可以被认为是已经排好序的了。
归并排序中最关键的步骤是合并,我们通过一个辅助过程Merge(A,p,q,r)完成,p,q,r为数组下标,满足p<=q<r。我们假定A[p,q]和A[q+1,r]都是已经排好序的,这样通过Merge过程合并两个子数组形成一个排好序的新数组A[p,r]来取代原有的数组。我们可以以玩扑克牌为例,假设桌子上有两堆牌面朝上的牌,每堆都是已经排好序的,最小的牌在最上面。合并操作的过程为,每次比较最上面的两张牌,将较小的牌牌面朝下放在桌子上,当一堆牌为空时,将另一堆牌直接放到输出堆中则完成了整个合并过程。为了避免每次都要检查两堆牌是否为空,我们在每堆牌的底部放置一个“哨兵牌”,我们以《无穷大》为哨兵值,哨兵牌不小于任何牌。下面的伪代码实现了这一思想:
伪代码:
Merge(A,p,q,r)
1. n1 = q - p + 1
2. n2 = r - q
3. Let L[1..n1+1] and R[1..n2+1] be new arrays
4. for i = 1 to n1
5. L[i] = A[p+i-1]
6. for j = 1 to n2
7. R[j] = A[q+j]
8. L[n1+1] = 无穷大
9. R[n2+1] = 无穷大
10. i = 1
11. j = 1
12. for k = p to r
13. if L[i] <= R[j]
14. A[k] = L[i]
15. i = i + 1
16. else A[k] = R[j]
17. j = j + 1
伪代码解释:
第一二行计算两个子数组的长度;
第三行创建两个新数组L,R,并且分别创建一个额外位置来存取哨兵;
第四行到第七行将两个子数组拷贝到两个新数组L,R中;
第八九行设置哨兵值;
第十二到十七行完成合并过程
完成这个辅助过程,我们就会很容易写出归并排序算法的伪代码:
MergeSort(A,p,r)
1. if p < r
2. q = (p+r)/2
3. MergeSort(A,p,q)
4. MergeSort(A,q+1,r)
5. Merge(A,p,q,r)
完整C代码如下:
![](/assets/blank.gif)
![](/assets/blank.gif)
/*Author:Terry Zhang*/ #include <stdio.h> #include <stdlib.h> #include <limits.h> #include <math.h>void Merge(int *A, size_t p, size_t q, size_t r); void MergeSort(int *A, size_t p, size_t r);int main(void) {size_t n;scanf_s("%d", &n);int *p = (int*)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d",p + i);}MergeSort(p, 0, n - 1);//outputint *p0 = p;for (size_t i = 0; i < n; i++){printf("%d ", *p0++);}printf("\n");//free free(p);return 0; }//Merge Sort void MergeSort(int *A, size_t p, size_t r) {if (p < r){size_t q = (p + r) / 2;MergeSort(A, p, q);MergeSort(A, q + 1, r);Merge(A, p, q, r);} }//auxiliary procedure void Merge(int *A, size_t p, size_t q, size_t r) {size_t n1 = q - p + 1;size_t n2 = r - q;size_t i = 0;size_t j = 0;int *L = (int *)calloc(n1 + 1, sizeof(int));int *R = (int *)calloc(n2 + 1, sizeof(int));for (i = 0; i < n1; i++){L[i] = A[p+i];}for (j = 0; j < n2; j++){R[j] = A[q+1+j];}L[i] = INT_MAX; //not L[i+1]R[j] = INT_MAX;i = 0;j = 0;for (size_t k = p; k <= r; k++){if (L[i] <= R[j])A[k] = L[i++];elseA[k] = R[j++];}free(L);free(R); }
View Code
六、快速排序
快速排序,名副其实,它是最广泛使用的一种快速排序算法。快速排序最坏情况下的时间复杂度和插入排序一样,但是它的平均时间复杂度却和归并排序一样。具体的原因可以参见《算法导论》,书中有详细的数学证明。
快速排序同归并排序一样,也是采用了分治思想。同样,我们以分治思想的三个必要步骤来解释这个算法:
1. 分解:数组A[p...r] 被分解成两个子数组A[p...q-1]和A[q+1...r],使得前一个数组中的每一个元素都小于等于A[q],后一个数组中的每一个元素都大于等于A[q],其中计算下标q也是划分过程的一部分
2. 解决:通过递归调用快速排序,对两个子数组进行排序
3. 合并: 因为子数组都是排好序的,所以不需要合并操作。
实质上,快速排序在划分的过程中也是在排序的过程中,这一点是它不同于归并排序的地方,也是不需要合并的原因。
快速排序的伪代码:
QuikSort(A,p,r)
1. if p < r
2. q = Partition(A,p,r)
3. QuickSort(A,p,q-1)
4. QuickSort(A,q+1,r)
由此可将,数组划分是快速排序算法最关键的部分
数组划分的伪代码:
Partition(A,p,r)
1. key = A[r]
2. i = p-1;
3. for j = p to r-1
4. if A[j] <= key
5. i = i + 1
6. exchange A[i] with A[j]
7. exchange A[i+1] with A[r]
8. return i + 1
伪代码讲解:
第一行我们设置数组最后一个元素为划分的主元key;
第二行初始化变量i,i在划分过程中用来表示比key小的元素应该放置的位置下标,即数组前面的部分;
第三到六行,j用来遍历数组中的元素,当发现比key小的元素A[j]时就将其依次放到数组前面部分,即与A[i] 交换,这样完成后数组前面部分都是小于等于key的元素;
第七行,将A[r]即key交换到所有小于等于key的元素的后面,这样整个数组就被key划分了一次,key也就恰好被放置到了它最终排序后应该在的位置;
第八行,返回划分位置下标
完整C代码如下:
![](/assets/blank.gif)
![](/assets/blank.gif)
#include <stdio.h> #include <stdlib.h> #include <time.h>int main(void) {size_t n;scanf_s("%d", &n);int *p = (int *)calloc(n, sizeof(int));for (size_t i = 0; i < n; i++){scanf_s("%d", p + i);}void QuickSort(int A[], int p, int r);QuickSort(p, 0, n-1);//outputint *p0 = p;for (size_t i = 0; i < n; i++){printf("%d ", *p0++);}printf("\n");free(p);return 0; }void QuickSort(int A[], int p, int r) {int Partition(int A[], int p, int r);if (p < r){int q = Partition(A, p, r);QuickSort(A, p, q - 1); //q-1 not qQuickSort(A, q + 1, r);} }void swap(int v[], int i, int j) {int temp;temp = v[j];v[j] = v[i];v[i] = temp; }int Partition(int A[], int p, int r) {int x = A[r];int i = p - 1;for (size_t j = p; j <= r-1; j++){if (A[j] <= x){i++;swap(A, i, j);}}swap(A, i + 1, r);return i + 1; }
View Code
以上我们的前提假设是输入数据的所有排列都是等概率的,所以我们每次选择数组或者子数组的最后一个元素为主元key,但实际情况这一假设并不总是成立,所以我们要考虑随机选取主元key,随机性的引入使得快读排序对所有的可能输入都会得到一个较好的期望性能。这就是快速排序的随机化版本。
伪代码很简单:
RandomPartition(A,p,r)
1. i= Random(p,r)
2. exchange A[r] with A[r]
3. return Partition(A,p,r)
随机划分的C代码如下:
int Randomized_Partition(int A[], int p, int r) {srand((unsigned)time(NULL));int t = rand() % (r - p) + p;swap(A, t, r);return Partition(A, p, r); }
转载于:https://www.cnblogs.com/dgzhangning/p/4127230.html
算法:六种比较排序算法相关推荐
- 数据结构之排序算法:内部排序算法的应用与比较
排序算法:内部排序算法的应用与比较 思维导图: 比较: 应用: 思维导图: 比较: 应用:
- 冒泡排序算法和选择排序算法比较
冒泡排序算法详细内容见→冒泡排序算法. 选择排序算法详细内容见→选择排序算法. 冒泡排序算法和选择排序算法的区别: 冒泡排序是比较相邻位置的两个数:而选择排序是按顺序比较,找出最大值或者最 ...
- 算法基础:排序算法之冒泡排序
算法基础:排序算法之冒泡排序 实现:数列有序排序 思想:已知一个数列,令数列中相邻的两个元素一一做比较,按照小大的顺序(或从大到小的顺序),如果前一个数比后一个数大(或后一个数比前一个数大),则互换( ...
- 伍六七带你学算法 进阶篇-排序算法
给定一个整数数组 nums,将该数组升序排列. 示例 1: 输入:[5,2,3,1] 输出:[1,2,3,5] 示例 2: 输入:[5,1,1,2,0,0] 输出:[0,0,1,1,2,5] 各排序算 ...
- python常见的排序算法_常见排序算法之python实现
1. 冒泡排序 时间复杂度为O(n^2), 稳定的排序算法 思路:一开始比较的区间是[0,n-1],依次比较相邻两数,哪个数大哪个数就放在后面,这样一次遍历数组后,最大的数会在数组的最后一个位置,然后 ...
- ef 排序string转int_排序算法之基本排序算法
基本排序算法 选择排序 选择排序(Selection sort)是一种简单直观的排序算法.它的工作原理是:第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未 ...
- 【每日算法】桶排序算法
1)算法简介 桶排序 (Bucket sort)或所谓的箱排序,是一个排序算法,工作的原理是将数组分到有限数量的桶子里.每个桶子再个别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排序 ...
- java8 lambda 排序算法,Java8中排序算法比较器的三种写法(使用lambda表达式实现Comparator比较器)...
在涉及到数组, 集合等这些地方经常会需要用到排序算法, 在Java中的Collections类中有sort方法, 除了需要传入一个Comparator比较器, 或者需要排序的类实现了Comparabl ...
- java排序算法总结_排序算法总结及Java实现
1. 整体介绍 分类 排序大的分类可以分为两种,内排序和外排序.在排序过程中,全部记录存放在内存,则称为内排序,如果排序过程中需要使用外存,则称为外排序.主要需要理解的都是内排序算法: 内排序可以分为 ...
最新文章
- [JavaScript] Set类型在JavaScript中的使用
- userinits.exe, wupcltr.exe的分析及解决办法
- Linux下C编程入门(7)
- 客户端跳转代码html5,HTML5跳转小程序wx-open-launch-weapp的示例代码
- 7.1使用Request获取HTTP请求头
- 修复mysql的view_MYSQL数据损坏修复方法
- Linux内核调试sysfs
- 如何看待没有学历的程序员?
- 网易邮箱账号遭公开叫卖;任正非谈华为接班人;中科协回应提名李彦宏增选院士 | 极客头条...
- html5+桌面推送,HTML5 Web Notifications 桌面推送小记
- RNN系列之四 长期依赖问题
- GBK和UNICODE
- 聚焦智能制造 香洲区产学研资对接合作活动 盈致科技成功牵手北理珠
- 【逻辑漏洞】短信轰炸
- MySQL倒序查询最后三条语句_MySQL 中 一条 order by index limit 语句的分析
- Linux用户安全及Linux PAM验证机制
- 如何用python写一个接口
- SpringBoot中使用AMQ的两种方式(Java配置、注解方式)
- 初学者最系统的前端学习之路
- win10 无法连接安卓手机,提示无法识别usb设备的解决方案
热门文章
- matlab/simulink光伏储能并网交直流发电系统仿真模型
- 互联网行业内卷的时代,12点前睡觉有错吗?
- linux下修改windows密码忘记,不记得linux/windows中mysqlroot密码万能修改方法
- latex 出现Missing { inserted
- 12V系统汽车电子保护及TVS管选型
- 游戏检测到计算机性能过低配色,win10系统电脑提示是否要更改配色方案来提高性能窗口的处理教程...
- UG创建图纸明细表失败的情况
- 网络:认识网络字节序
- 山西阳泉中考计算机科目,2018阳泉中考科目及分值
- 面试题:谈谈对mysql性能优化的理解