标签:顺序存储 不用 运行时间 直接插入排序 复杂度 void 单元 第一个 解决
分类 | 排序算法 | 改进思路 | 最好情况 | 平均时间复杂度 | 最坏情况 | 空间复杂度 | 稳定性 |
---|---|---|---|---|---|---|---|
插入排序 | 直接插入排序 | 基本排序方法 | O(n) | O(\(n^2\)) | O(\(n^2\)) | O(1) | 稳定 |
折半插入排序 | 确定有序序列的插入位置 | O(\(nlog_2n\)) | O(\(n^2\)) | O(\(n^2\)) | O(1) | 稳定 | |
希尔排序 | 利用直接插入的最好情况 | O(\(n^{1.3}\)) | NA | O(\(n^2\)) | O(1) | 不稳定 | |
交换排序 | 冒泡排序 | 基本排序方法 | O(n) | O(\(n^2\)) | O(\(n^2\)) | O(1) | 稳定 |
快速排序 | 交换不相邻元素 | O(\(nlog_2n\)) | O(\(nlog_2n\)) | O(\(n^2\)) | O(\(nlog_2n\)) | 不稳定 | |
选择排序 | 简单选择排序 | 基本排序方法 | O(\(n^2\)) | O(\(n^2\)) | O(\(n^2\)) | O(1) | 不稳定 |
堆排序 | 将待排序列转化为完全二叉树 | O(\(nlog_2n\)) | O(\(nlog_2n\)) | O(\(nlog_2n\)) | O(1) | 不稳定 | |
归并排序 | 2路归并排序 | 合并每个有序序列 | O(\(nlog_2n\)) | O(\(nlog_2n\)) | O(\(nlog_2n\)) | O(n) | 稳定 |
基数排序 | 基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) | 稳定 |
有序序列L[1...i-1] | L(i) | 无序序列L[i+1...n] |
---|
void InsertSort(ElemType A[], int n) {
int i, j;
for(i = 2; i <= n; i++) { //依次将A[2]~A[n]插入到前面已排序序列
if(A[i].data < A[i-1].data) { //若A[i]的关键码小于有序序列的最后一个元素(有序序列中的最大值)(即其前驱),需将A[i]插入有序表;大于有序序列的最后一个元素,则不用从头插入,并入有序序列(本来就排在有序序列之后),成为有序序列新的最大值(最后一个)
A[0] = A[i]; //复制为哨兵,A[0]不存放元素
for(j = i - 1; A[0].data < A[j].data; --j) { //从后往前查找待插入位置
A[j+1] = A[j]; //向后挪位
}
A[j+1] = A[0]; //复制到插入位置
}
}
}
空间效率:O(1)
仅使用了常数个辅助单元。
稳定性:稳定
由于每次插入元素时,总是从后向前先比较在移动,所以不会出现相同元素相对位置发生变化的情况。
适用性:
适用于顺序存储和链式存储的线性表。
适用于基本有序的排序表和数据量不大的排序表。
为链式存储时,可以从前往后查找指定元素的位置。
大部分排序算法都仅适用于顺序存储的线性表。
基本思想:
将比较和移动操作分离,即先折半查找出元素的待插入位置,然后统一地移动待插入位置之后的所有元素。
void InsertSort(ElemType A[], int n) {
int i, j, low, high, mid;
for(i = 2; i <= n; i++) { //依次将A[2]~A[n]插入前面的已知排序序列
A[0] = A[i]; //将A[i]暂存到A[0]
low = 1; //设置折半查找的范围
high = i-1;
while(low <= high) { //折半查找(默认递增有序)
mid = (low + high) / 2; //取中间点
if(A[mid].data > A[0].data) { //查找左半边
high = mid - 1;
}else { //查找右半边
low = mid + 1;
}
//查找结束后,high<low,k在high和high+1之间,所以顶替high+1位置,把high+1后面的元素后移
for(j=i-1; j>=high+1; --j) {
A[j+1] = A[j]; //统一后移元素,空出插入位置
}
A[high+1] = A[0]; //插入
}
}
}
空间效率:O(1)
仅使用了常数个辅助单元。
稳定性:稳定
适用性:适用于顺序存储的线性表
基本思想:
先将待排序表分割成若干形如L[i, i+d, i+2d, ..., i+kd]的“特殊”子表,分别进行直接插入排序,当整个表中的元素已呈“基本有序”时,再对全体记录进行一次直接插入排序
//对顺序表做希尔插入排序,本算法和直接插入排序相比,做了以下修改:
//1. 前后记录的位置增量是dk,不是1
//2. A[0]只是暂存单元,不是哨兵,当j<=0时,插入位置已到
void ShellSort(ElemType A[], int n) {
for(dk=n/2; dk>=1; dk=dk/2) { //步长变化
for(i=dk+1; i<=n; ++i) {
if(A[i].data < A[i-dk].data) { //需将A[i]插入有序增量子表
A[0] = A[i]; //暂存在A[0]
for(j=i-dk; j>0 && A[0].data<A[j].data; j-=dk) {
A[j+dk] = A[j]; //记录后移,寻找插入的位置
}
A[j+dk] = A[0]; //插入
}
}
}
}
稳定性:不稳定
但相同关键字的记录被划分到不同的子表时,可能会改变它们之间的相对次序。
适用性:仅适用于线性表为顺序存储的情况。
基本思想:
假设待排序表长为n,从后往前(或从前往后)两两比较相邻元素的值,若为逆序(即A[i-1] > A[i]),则交换它们,知道序列比较完。称之为一趟冒泡,即将最小的元素交换到待排序列的第一个位置(关键字最小的元素如气泡一般逐渐往上“漂浮”直至“水面”)。下一趟冒泡时,前一趟确定的最小元素不在参与比较,待排序列减少一个元素。这样最多做n-1趟冒泡就能把所有元素排好序。
//用冒泡排序法将序列A中的元素按从小到大排列
void BubbleSort(ElemType A[], int n) {
for(i=0; i<n-1; i++) { //趟数
flag = false; //表示本趟冒泡是否发生交换的标志
for(j=n-1; j>i; j--) { //一趟冒泡过程
if(A[j-1].data > A[j].data) { //若为逆序
swap(A[j-1], A[j]); //交换
flag = true;
}
}
if(flag == false) { //本趟遍历后没有发生交换(前面一个元素均小于后面一个),说明表已经有序
return;
}
}
}
稳定性:稳定
i>j且A[i].data==A[j].data时,不会交换两个元素
适用性:适用于线性表
小L[1...k-1] | L(k) | 大L[k+1...n] |
---|
基本思想:
基于分治法,在待排序表L[1...n]中任取一个元素pivot(枢轴)作为基准,通过一趟排序将待排表划分为两个独立的两部分L[1...k-1]和L[k+1...n],使得L[1...k-1]中所有元素小于pivot,L[k+1...n]中的所有元素大于等于pivot,则pivot放在了其排序后的最终位置L(k)上,这个称为一趟快速排序。而后分别递归地对两个子表重复上述过程,直至每个部分内只有一个元素或空为止,即所有元素放在了其最终位置上。
//划分算法(一趟排序过程)
int Partition(ElemType A[], int low, int high) {
ElemType pivot = A[low]; //将当前表中第一个元素设为枢轴值,对表进行划分
while(low < high) {
while(low<high && A[high]>=pivot) { //当high中的值已经比pivot大,则不移动
--high;
}
//循环结束时,high中的元素比pivot小
A[low] = A[high]; //将小值移动到左端
while(low<high && A[low]<=pivot) { //当low中的值已经比pivot小,则不移动
++low;
}
//循环结束时,low中的元素比pivot大
A[high] = A[low]; //将大值移动到右端
}
//循环结束时,low==high,已经是pivot的最终位置
A[low] = pivot; //放入最终位置
return low; //返回存放枢轴的最终位置
}
void QuickSort(ElemType A[], int low, int high) {
if(low >= high) { //不能划分了,递归出口
return;
}
int pivotpos = Partition(A, low, high); //划分
QuickSort(A, low, pivotpos-1); //依次对两个子表进行递归排序划分
QuickSort(A, pivotpos+1, high);
}
稳定性:不稳定
在划分算法中,若右端区间有两个关键字相同,且均小于基准值的记录,则在交换到左区间后,它们的相对位置会发生变化。
适用性:适用于顺序存储的线性表
基本思想:
假设排序表为L[1...n],第i趟排序即从L[i...n]中选择关键字最小的元素与L(i)交换,每一趟排序可以确定一个元素的最终位置,这样经过n-1趟排序就可使得整个排序表有序。
//对表A做简单选择排序,A[]从0开始存放元素
void SelectSort(ElemType A[], int n) {
for(i=0; i<n-1; i++) { //一共进行n-1趟
min = i; //记录最小元素的位置
for(j=i+1; j<n; j++) { //在A[i...n-1]中选择最小的元素
if(A[j] < A[min]) { //更新最小元素位置
min = j;
}
if(min != i) { //与第i个位置交换
swap(A[i], A[min]);
}
}
}
}
空间效率:O(1)
仅使用常数个辅助单元
稳定性:不稳定
在第i趟找到最小元素后,和第i个元素交换,可能会导致第i个元素与其含有相同关键字元素的相对位置发生改变。
适用性:适用于顺序存储的线性表
基本思想:
具体实现:
空间效率:O(1)
仅使用了常数个辅助单元。
稳定性:不稳定
进行筛选时,有可能把后面相同关键字的元素调整到前面。
适用性:线性表
基本思想:
“归并”的含义是将两个或两个以上的有序表组合成一个新的有序表。
假定待排序表含有n个记录,则可将其视为n个有序的子表,每个子表的长度为1,然后两两归并,得到\(?n/2?\)个长度为2或1的有序表;再两两归并……如此重复,知道合并成一个长度为n的有序表位置,这种排序方法称为2路归并排序
递归形式的2路归并排序算法是基于分治的。
分解:将含有n个元素的待排序表分成各含\(n/2\)个元素的子表,采用2路归并排序算法对两个子表递归地进行排序。
合并:合并两个已排序的子表得到排序结果。
具体实现:
空间效率:O(n)
Merge()操作中,辅助空间刚好占用n个单元
稳定性:稳定
由于Merge()操作不会改变相同关键字记录的相对次序
适用性:顺序存储的线性表
基本思想:
将每个位数排好序,由低到高(由高到低)
具体实现:
空间效率:O(r)
一趟排序需要的辅助空间为r(r个队列),但以后的排序中会重复使用这些队列
稳定性:稳定
对于基数排序算法而言,按位排序是必须是稳定的,所以保证了基数排序的稳定性
适用性:顺序存储的线性表
标签:顺序存储 不用 运行时间 直接插入排序 复杂度 void 单元 第一个 解决
原文地址:https://www.cnblogs.com/blknemo/p/11234908.html