整体把握七大排序,就靠它了
作者:互联网
目录
notice:
待排序区(to be sorted),简称为待排区;已排序区(sorted),简称为已排区。
开始时,数列的已排区为空,待排区为整个数列集合。
冒泡排序
-
工作原理
在数列待排区内,依次比较两个相邻元素大小,若逆序则交换,直至无逆序为止,得到有序数列。
-
实现思想
每一轮相邻元素的比较和交换,得到最小(或最大)元素到数列的末端。即在数列末端的已排区逐渐壮大。
-
动图视频
-
伪代码
//从小到大
void bubbleSort(int n, keyType arr[]) {
for(index i=0; i<n-1; i++) {
for(index j=0; j<n-1-i; j++) {
if (arr[j] > arr[j+1])
exchange(arr[j],arr[j+1]);
}
}
}
-
特点
-
复杂度与初始排序有关:可以对伪代码进行优化,若某趟比较都没有逆序对,则停止排序。
-
最好O(n),最坏/平均O(n^2),空间O(1),原址:优化后的冒泡排序在初始数列有序时,最好的时间复杂度为O(n);只用常数个额外元素空间存储临时数据,空间为O(1)。
-
稳定:每次相邻的比较和交换保证了重复数据之间的相对顺序
-
适用于小规模数据:元素少时比较快,随着元素增多,复杂度高成为瓶颈。
插入排序
-
工作原理
在数列待排区中,依次拿出一个元素插入到已排区内,待排区元素减一,已排区保持有序。如此直至待排区为空,得到已排区的有序数列。
-
实现思想
在数列待排区中依次拿出一个元素,插入到保持已排区有序的适当位置上。
-
动图视频
-
伪代码
//从小到大
void insertSort(int n, keyType arr[]) {
for(index i=1; i<n; i++)
for(index j=i; 0<j && arr[j-1] > arr[j]; j--)
exchange(arr[j-1],arr[j]);
}
-
特点
-
复杂度与初始排序有关:大部分数据已有序时,时间复杂度较优。
-
最好O(n),最坏/平均O(n^2),空间O(1),原址:若数列初始有序,复杂度最好为O(n)。
-
稳定:每次拿元素和插入时,都保持数据的相对顺序,并且插入时可以保证重复数对靠后的重复数一定插入在靠前重复数的后面。
-
适用于小型数据
归并排序
-
工作原理
将目标数列折半划分至每组元素数量为1,再两两合并排序。先使子数列有序,再使相邻子数列有序,最后将两个有序表合并为一个有序表。
-
实现思想
分治思想。从上而下,在宏观上将数列分成一块块数据,直至个数为1的子序列。从下而上,将相邻子序列逐步排序合并。
-
动图视频
-
伪代码
void mergeSort(keyType arr[], int left,int right) {
if(left < right) {
int mid = mid(left, right);
mergeSort(arr,left, mid);
mergeSort(arr,mid+1, right);
merge(arr,left, mid, right);
}
}
void merge(keyType arr[], int left, intmid, int right) {
int nl = mid-left+1;
int nr = right-mid;
arrLeft= Malloc(nl);
arrRight= Malloc(nr);
arrLeft[0...nl] = arr[left...mid];
arrRight[0...nr] = arr[mid+1...right];
arr = compareAndMerge(arrLeft, arrRight);
delete[] arrLeft, arrRight;
}
-
特点
-
复杂度与初始排序无关:什么情况都是先分解,再排序合并。
-
最好/最坏/平均O(nlgn),空间O(n),不原址
-
稳定:分治之后,每块的数据在宏观相对有序,每块内的数据也能保证相对有序。
-
数据分割可管理,适用于中大、海量数据
-
每次合并时间复杂度为O(n),深度|lgn|,在最坏情况下是最快的排序。
选择排序
-
工作原理
在数列待排区内,选出最小(或最大)元素,放在待排区的起始位置,待排区元素减一。如此直至待排区为空,得到已排区的有序数列。
-
实现思想
在待排区内,遍历中选出局部最小(或最大)元素,遍历完待排区,得到待排区全局最小(或最大)元素,放至待排区起始位置,待排区元素减一。
-
细节
数列可以用数组或链表底层实现,不同的数据结构具体操作细节不一。例如选局部最小(或最大)元素,数组通常用记下标的方式,链表则需要一个指针指向这个元素。
-
动图视频
-
伪代码
//从小到大
void selectSort(int n, keyType arr[]) {
index i,j,smallest;
for (i=0; i<n; i++) {
smallest= i;
for(j=i+1; j<n; j++)
if (arr[smallest] > arr[j])
smallest = j;
exchange(arr[i],arr[smallest]);
}
}
-
特点
-
复杂度与初始排序无关:每次在待排区内遍历一遍选举最小(最大)元素,减少一个待排区的元素。
-
最好/最坏/平均时间O(n^2),空间O(1),原址:待排区的时间复杂度为(n)+(n-1)+(n-2)+…+(2)+(1)=(n+1)*n/2 = O(n^2),
-
不稳定:例如从小到大排序,在待排区中,若重复数对后面有比其更小的元素,则有可能在新一轮选择中,更小的元素和靠前的重复数交换,两个重复数的相对位置发生变化,变得不稳定。
-
例如数列集合(…,a,…,b1,…,b2,…,c;其中c<b1=b2),一轮选择后变成(…,a,…,c,…,b2,…,b1),此时a<c。之所以有可能,是因为这轮选择后也可能变成(…,c,…,b1,…,b2,…,a),此时c<b1<a,这个比重复数更小的数c没有潜伏在重复数对之后,也就没有了不稳定的因素。
-
适用于小规模数据
希尔排序
-
工作原理
是插入排序的变种。把数列按步长分成若干组,每组内进行插入排序。随着步长逐渐减小至1,完成排序。
-
实现思想
通过这种策略使得整个数列在初始阶段在宏观上基本有序,小(或大)的基本在前,大(或小)的基本在后;当步长为1 时,只需微调。
-
细节
希尔排序的运行时间依赖于步长的选择。只要最终的步长为1,就可以工作。是插入排序的改进版本。最坏和平均相差不多,下界是O(nlg2n)。
数列一般用数组底层实现,下标根据步长很容易得到。
-
特点
-
复杂度与初始排序有关:对于几乎已经排好序的数据,可以达到线性排序的效率。
-
最好O(n^1.3),最坏O(n^2),平均O(nlg2n),空间O(1),原址。不稳定:插入排序是稳定的,但是分步长组合成的数据进行插入排序,宏观是不稳定的。
-
适用于小中规模数据,或几乎已经排好序的数据。
堆排序
-
工作原理
将数组构造成一个最大堆,将堆顶的数与数组末尾进行交换,剩余的数重新构造成堆,如此反复。
-
实现思想
针对堆的性质设计的算法。堆是具有以下性质的完全二叉树:每个节点的值都大于或等于其左右孩子节点的值,称为最大堆;每个节点的值都小于等于其左右孩子节点的值,称为最小堆。将堆中节点按层进行编号,映射至数组中。
-
C++代码
//end开区间
void makeHeap(vector<int>&nums, int start, int end) {
if(start >= len)
return;
int dad = start, son = (start<<1) + 1;
//儿节点有效
while(son < end) {
//nums[son]是较大儿节点
if(son+1 < end && nums[son] < nums[son+1])
++son;
if(nums[dad] < nums[son])
return;
else{
//父小节点调到儿节点
//小节点以下有可能不满足条件
swap(nums[dad],nums[son]);
dad = son;
son = (dad<<1) + 1;
}
}
}
void heap(vector<int>& nums) {
int len = nums.size();
//由最下边的一个父节点开始,构造堆
//大节点一层层上浮:等差数列时间复杂度为O(n)
for(int i = len/2-1; i >= 0; --i)
makeHeap(nums,i, len);
//开始n-1次下沉,再n-1次构造
for(int i = len-1; i > 0; --i) {
swap(nums[0],nums[i]);
makeHeap(nums,0, i); //重建堆O(lgn)
}
}
-
特点
-
复杂度与初始排序无关:每次排序,都是先构造堆O(n),下沉n-1次,再重构堆O(lgn)
-
最好/最坏/平均O(nlgn),空间O(1),原址
-
不稳定:在数组的二叉树操作中,无法保证重复数的相对顺序
-
适合中大规模数据
快速排序
-
工作原理
将数列划分为两个(可能为空)子数列Al[p,…,q-1]和Ar[q+1,…,r],使得Al中的每个数都小于等于A[q],而A[q]也小于等于Ar中的每一个元素。其中,计算下标q也是划分过程中的一部分。
-
实现思想
分治思想。选定分界值,即主元,通过一趟排序,将待排序的数据分成两个独立的部分,其中一部分的所有数据比另一部分的所有数据都小。重复分治,直至数列整体有序。
实现时,往往将选定的主元放置数列的一端,然后从另一端开始比较交换。
-
细节
快排的运行时间依赖于划分是否平衡(主元的选择),划分平衡时与归并一样,否则接近但稍逊于插入排序。主元选取优化:
-
三数取中,从子数组中随机选出三个元素,取中间的数为主元。常选首位、中间和末尾的数
-
随机选取主元,这样最坏情况下不再依赖输入数据,而在于随机函数。缺点:数据中有很多相同数据时,随机化效果直接减弱。
-
动图视频
-
伪代码
//从小到大,将主元放置数列左端,小于等于主元的数放在主元左边
void quickSort(keyType arr[], int left, int right) {
if (left < right) {
int x = partition(arr, left, right);
quickSort(arr, left, x-1);
quickSort(arr, x+1, right);
}
}
void partition(keyType arr[], int left, int right) {
int index= selectPrincipal(arr);
int principal = exchange(arr[left],arr[index]);
while (left < right) {
while (left < right &&principal < arr[right])
right--;
exchange(arr[left], arr[right]);
while (left < right &&arr[left] <= principal)
left++;
exchange(arr[left], arr[right]);
}
arr[left] = principal;
return left;
}
-
特点
-
复杂度与初始排序有关:当数组完全有序(正序或逆序)时,复杂度仍然为O(n^2),而此时插入排序为O(n)。基本有序时不适合。
-
最好/平均O(nlgn),最坏O(n^2),空间O(lgn),原址:最好情况下,划分平衡,只要划分是常数比例的(递归树深度为lgn,每一层时间代价都是O(n),算法的运行时间总为O(nlgn)。最坏情况下,两个子问题分别包含n-1和1个数据,即每次只排除一个数据,是一个斜树。
-
不稳定:有重复数的时候,无法保证选取的主元总是靠前或者靠后的重复数,这样无论规定主元左边子序列是小于还是小于等于主元,都有可能交换两个重复数的相对位置。只要有不稳定的可能,就是不稳定的算法。
-
平均性能好,适用于中大规模数据。
总结
排序 | 最好T | 平均T | 最坏T | 空间 | 原址 | 稳定 |
冒泡 | O(n) | O(n^2) | O(1) | √ | √ | |
插入 | O(n) | O(n^2) | O(1) | √ | √ | |
归并 | O(nlgn) | O(n) | × | √ | ||
选择 | O(n^2) | O(1) | √ | × | ||
希尔 | O(n^1.3) | O(nlg2n) | O(n^1.5) | O(1) | √ | × |
堆 | O(nlgn) | O(1) | √ | × | ||
快排 | O(nlgn) | O(n^2) | O(lgn) | √ | × |
每个人都是独特的,只是有些独特你想靠近一点。我是一朵小黄花啊,做一点,做的更好一点。欢迎关注、分享、收藏和点赞,你的每一份支持,都是我输出的动力。
扫一扫,加个关注,一起成长哇。
原创文章,禁止转载,如有需求,请私信联系。
标签:arr,数列,int,七大,排区,排序,把握,left 来源: https://blog.csdn.net/sy_123a/article/details/115384796