1.排序移动方式分为直接移动和逻辑移动。(直接移动:直接交换存储数据的位置;逻辑移动;不会移动数据存储位置,仅改变指向这些数据的辅助指针的值。)
2.排序按照数据量的多寡和所使用的内存可分为内部排序和外部排序(内部排序:排序期间元素全部存放在内存中的排序;外部排序:指在排序期间元素无法完全同时放在内存中,必须在排序的过程中根据要求不断地在内,外存之间移动的排序)。根据排序后数值相同的数的位置与之前位置的比较又可以分为稳定排序与不稳定排序。
3.
算法种类 | 时 间 复 杂 度 | 空间复杂度 | 是否稳定 | ||
最好情况 | 平均情况 | 最坏情况 | |||
直接插入排序 | O(n) | O(n^2) | O(n^2) | O(1) | t |
冒泡排序 | O(n) | O(n^2) | O(n^2) | O(1) | t |
简单选择排序 | O(n^2) | O(n^2) | O(n^2) | O(1) | f |
希尔排序 |
| O(1) | f | ||
快速排序 | O(nlog 2 n) | O(nlog 2 n) | O(n^2) | O(log 2 n) | f |
堆排序 | O(nlog 2 n) | O(nlog 2 n) | O(nlog 2 n) | O(1) | f |
2路归并排序 | O(nlog 2 n) | O(nlog 2 n) | O(nlog 2 n) | O(n) | t |
基数排序 | O(d(n+r)) | O(d(n+r)) | O(d(n+r)) | O(r) | t |
4.
排序 |
基本概念 | 稳定性 | |
衡量标准:时/空复杂度 | |||
内部排序 | 插入排序:基本思想在于每次将一个待排序的记录,按其关键字大小插入到前面已经排好序的子序列中,直到全部记录插入完成 | ||
折半插入排序 | |||
交换排序:根据序列中两个元素关键字的比较结果来对换这两个记录在序列中的位置。 | |||
选择排序:每一趟(例如第i趟)在后面n-i+1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序的只剩一个,就不用再选了。 | |||
归并排序:归并的含义是将两个或两个以上的有序表组合成一个新的有序表。假定待排序表含有n个记录,则可以看成是n个有序的子表,每个子表的长度为1,然后两两归并,得到n/2个长度为2或1的有序表;再两两归并,。。。。。如此重复直到合并成一个长度为n的有序表,这种排序方法称为2路归并排序。 | |||
基数排序:不是基于比较进行排序,而是采用多关键字排序思想,借助分配和收集两对操作对单逻辑关键字进行排序。分为最高位优先MSD排序,和最低位优先LSD排序。 | |||
外部排序 | 多路归并排序 |
有序序列L[1...i-1] | L(i) | 无序序列L[i+1.......n] |
为了实现将元素L(i)插入到已有序的子序列L(1.....i-1)中,我们需要执行以下操作(L[ ]表示一个表,L( )表示一个元素):
-
查出L(i)在L[1....i-1]中的插入位置k;
-
将L[k....i-1]中所有元素全部后移一个位置。
-
将L(i)复制到L(k)。
为了实现对L[1.....n]的排序,可以将L(2)~L(n)依次插入到前面已排序好的子序列中,初始假定L(1)是一个已经排序好的子序列。上述操作执行n-1次就能得到一个有序的表。插入排序在实现上通常采用就地排序(空间复杂度为O(1)),因而在从后往前的比较过程中,需要反复把已排序的元素逐步向后挪位,为新元素提供插入空间。
Void InsertSort ( ElemType A[ ],int n)
{
Int i,j;
for(i=2;i<=n;i++) //依次将A[2]~A[n]插入到前面已排好序列
If(A[i].key<A[i-1].key) //若A[i]的关键码小于其前驱,需将A[i]插入有序表
{
A[0]=A[i]; //复制为哨兵,A[0]不存放元素
for(j=i-1;A[0].key<A[j].key;--j) //从后往前查找待插入位置
A[j+1]=A[j]; //向后移位
A[j+1]=A[0]; //复制到插入位置
}
}
基本思想:先将待排序表分割成若干个形如 L[i,i+d,i+2d,...i+kd] 的特殊子表,分别进行直接插入排序,当整个表中元素已呈基本有序时,再对全体记录进行一次直接插入排序。
过程如下:先取一个小于n的步长d1,把表中全部记录分为d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组中进行直接插入排序;然后取第二个步长d2<d1,重复此时已经能够具有较好的局部有序性,故可以很快得到最终结果。到目前为止,尚未求得一个最好的增量序列,希尔提出的方法是d1=n/2,d(i+1)=di/2;并且最后一个增量等于1。
void ShellSort(ElemType A[], int n)
{
//对顺序表做希尔插入排序,本算法和直接插入排序相比,作了以下修改:
//1.前后记录位置的增量是dk,不是1
//2.r[0]只是暂存单元,不是哨兵,当j<0时,插入位置已到
for (dk = len / 2; dk >= 1;dk=dk/2) //步长变化
for (i = dk + 1; i <= n;++i)
if (A[i].key < A[i - dk].key) //需将A[i]插入有序增量子表
{
A[0] = A[i]; //暂存在A[0]
for (j = i - dk; j>0 && A[0].key < A[j].key; j -= dk)
A[j + dk] = A[j]; //记录后移,查找插入的位置
A[j + dk] = A[0]; //插入
} //if
}
基本思想:假设待排表长为n,从后往前(或从前往后)两两比较相邻元素的值,若为逆序,则交换它们,直到序列比较完。我们称它为一趟冒泡,结果将最小的元素交换到待排序列的第一个位置()下一趟冒泡,前一趟确定的最小元素不再参与比较,待排序列减少一个元素,每趟冒泡的结果把序列中的最小元素放到了序列的最终个位置,。。。。。,这样最多做n-1趟冒泡就能把所有元素排好。
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].key > A[j].key) //若为逆序
{
swap(A[j - 1], A[j]); //交换
flag = true;
}
if (flag == false)
return; //本趟遍历没有交换说明排序完毕
}
}
快速排序是对冒泡排序的一种改进。其基本思想是基于分治法的:在待排表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;
A[low] = A[high]; //将比枢轴值小的元素移动到左端
while (low < high && A[low] <= pivot) ++low;
A[high] = A[low]; //将比枢轴值大的元素移动到右端
}
A[low] = pivot; //枢轴元素存放的最终位置
return low; //返回存放枢轴的最终位置
}
void QuickSort(ElemType A[], int low, int high)
{
if (low < high) //递归跳出条件
{
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趟排序使整个表有序。
void SelectSort(ElemType A[], int n)
{
//对表A进行简单选择排序,A[]从0开始存放元素
for (i = 0; i < n - 1; i++)//共进行n-1趟
{
int min, i, j;
min = i;//记录最小元素位置
for (j = i + 1; j < n;j++)//在A[i....n-1]中选择最小的元素
if (A[j] < A[min])
min = j;//更新最小元素位置
if (min != i)
swap(A[i],A[min]);//与第i个位置元素交换
}
}
堆排序
归并排序