排序
将数据表(a1,a2,……,an)调整为按关键字从小(大)到大(小)排列的过程。
几个术语——
-
增排序
-
减排序
-
单关键字/多关键字
-
内部排序:所有数据在内存
-
外部排序:部分数据在内存,部分数据在外存(涉及到内外存的交换)
-
稳定排序:
排序过程中关键字相同的元素的相对次序不变。 -
不稳定排序:
排序过程中关键字相同的元素的相对次序发生变化。
交换排序
排序过程是交换元素位置
冒泡排序
从一端开始,逐个比较相邻的两个元素,发现倒序即交换。
这里按从后往前(从下往上)逐个比较相邻元素
红线上面是已经排好的,无需再动
void bubble_sort(elementtype A[ ],int n){
for (i=1; i<=n-1; i++ )
for (j=n; j>=i+1;j-- )
if (A[j].key<A[j-1].key)
A[j]<==>A[j-1];
}
这个算法可以改进一点,就是如果我有一次逐个比较发现无需交换,那么这个序列就已经是有序的了。基于这个想法,有以下代码:
void bubble_sort(elementtype A[ ],int n){
i=1;
do{
exchanged=FALSE; //标志,一旦有一次没有发生交换就退出
for (j=n; j>=i+1, j--)
if (A[j].key<A[j-1].key){
A[j]<==>A[j-1];
exchanged=TRUE; }
i++;
}while (i<=n-1 && exchanged==TRUE );
}
快速排序
基本思想:
将数据划分为两部分,左边的所有元素都小于右边的所有元素;然后,对左右两边进行快速排序。
划分方法
-
选定一个参考点(中间元素),所有元素与之相比较,小的放左边,大的放右边。
-
每次都固定一个位置不变的
具体划分方法:
选择第一个元素作为中间元素。
划分:
- 先保存该元素的值到其它位置,腾出其空间。
- 从后往前搜索一个比中间数小的元素,并将其放置到前面的这个空位上。
- 从前往后搜索一个比中间数大的元素,并将其放置到后面的这个位置上。
重复2,3,直到两边搜索的空位重合,此时将中间元素放在该空位中。
选定第一个数作为分割点,那么这里就形成了一个空,min与max不断从两边往中间靠,他们用于标记当前元素的下标和交换后原来空格的位置,max从最后面开始不断往前面走,如果遇到比分割点小的就交换该数与空格的位置,然后min从交换前空格的位置的下一个开始往后走,一旦遇到比分割点大的就交换与空格的位置,然后max从交换前空格的前一个开始往前走,重复这样的过程,直到min==max。然后以标记点为分割点对两边继续递归执行上述过程,出口是min==max
void partition(elementtype A[n],int s,int t,int *cutpoint)
//对数组A中下标为s~t的子表进行划分
{ x=A[s]; //保存中间元素,腾出空位
min=s; max=t;
while ( min!=max ) {
while ( min<max && A[max].key>x.key ) max--;
if ( min<max ) { A[min]=A[max]; min=min+1; }
while ( min<max && A[min].key<=x.key ) min++;
if ( min<max ) { A[max]=A[min]; max=max-1; }
}
A[min] = x; *cutpoint=min;
}
前两句那个i<j
可以去掉,大不了原地交换一次,但是后面两句不能去掉,可能会导致错误。
void Quick_sort(elementtype A[n], int s, int t)
{
if (s<t ) {
partition(A,s,t,*i); //划分
Quicksort(A,s,*i-1); //对前面子表快速排序
Quicksort(A,*i+1,t); //对后面子表快速排序
}
}
这是插入排序
排序过程是插入
这是直接插入排序
将待排序表看作左右两部分,其中左边为有序区,右边为无序区,整个排序过程就是将右边无序区中的元素逐个插入到左边的有序区中,以构成新的有序区。
红线左侧是有序区,括号外面是选中的数,不断与有序区的比较大小,那些数字不断后移直到找到合适的位置。蓝色和黑色的5表明这是稳定排序。
void insertsort(elementtype A[]){//这里浪费了A[0]
for ( i=2; i<=n; i++){
temp=A[i]; j=i-1;
while ( (A[j].key>temp.key&&j>=1)
{A[j+1] =A[j]; j--;}
A[j+1]=temp;
}
}
监视哨写法
void insertsort(elementtype A[]){
for ( i=2; i<=n; i++) {
A[0]=A[i]; j=i-1;
while (A[j].key>A[0].key )
{A[j+1]=A[j]; j--;}
A[j+1]=A[0] ;
}
}
有序时:只与前一个比较一次就确定不交换,移动的话这两个也算
所以是2(n-1)
逆序时:比较次数,很容易以为是1+……+n-1=(n(n-1))/2,但是别忘了最后退出循环的时候与j与1比较那次也算,所以要加上n-1,为((n+2)(n-1))/2,移动次数的话1+……+n-1,还要加上2(n-1),所以是((n+1)(n-1))/2.
这是希尔排序
上面不是说,直接插入排序对比较有序的数据很有效,那么就先让他有序一点。
思路是我先让每隔d个数有序(隔k个数挑出来的数是有序地),然后最后让d=1保证其有序,那么就是对编号隔k个的先直接插入排序,最后整个来直接插入排序。
void shell_sort(elementtype A[]){//依然是浪费A[0]
d=n/2;
while(d>0) {
for(i=d+1;i<=n;i++) {
x=A[i];j=i-d;
while(j>0&&x.key<A[j].key ){
A[j+d].key=A[j].key; j=j-d;
}
A[j+d]=x;
}
d=d/2;
}
}
关注那个i++,如果改成i+=d的话就只是排一次了,不能像演示那样逐个排。
选择排序
直接选择排序
基本思想:
在待排序子表中找出最大(小)元素,并将该元素放在子表的最前(后)面。
void select_sort(elementtype A[n])
{ for (i=0; i<n-1; i++ )
{
min=i;
for ( j=i+1; j<n; j++ )
if ( A[j].key < A[min].key )
min=j;
if (min!=i)
A[min]<==>A[i];
}
}
像这种远距离交换位置的一般都是不稳定的。
锦标赛排序
基本思想
也称为树形选择排序(Tree Selection Sort),是一种按照锦标赛的思想进行选择排序的方法。
- 首先对n个记录进行两两比较,然后优胜者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。
这个过程可以用一棵有n个叶子结点的完全二叉树表示。 - 根节点中的关键字即为叶子结点中的最小关键字。
- 在输出最小关键字之后,欲选出次小关键字,仅需将叶子结点中的最小关键字改为“最大值”,如∞, 然后从该叶子结点开始,和其兄弟的关键字进行比较,修改从叶子结点到根的路径上各结点的关键字, 最后根结点的关键字即为次小关键字。
刚开始比一次,然后将最小的那个置为∞,然后从刚置无穷那个地方开始再比一次(另一边不用比,因为没有变化结果是一样的),选出最小的,回到上面的操作。
堆排序
堆排序:利用堆进行的排序。
定义:称n个元素组成的序列(a1,a2,…,an) 为堆,
当且仅当满足下面关系。(其中ki是元素ai的关键字)
(1) ki≤k2i ; ki≤k2i+1
或
(2) ki≥k2i ; ki≥k2i+1 ( 2i≤n;2i+1≤n)
就是一个弱化版的二叉排序树
若序列(a1,a2,…,an)是堆,则堆顶(完全二叉树的根)必为序列中的最小或最大值。
将根最大的堆称为大根堆,
根最小的堆称为小根堆.
实现堆排序还需要解决两个问题:
(1)如何由一个无序序列建成一个堆?
(2)如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
调整成新堆
在输出堆顶元素之后,以堆中最后一个元素替代之,此时根结点的左、右子树均为堆,则仅需自上至下进行调整即可。
我们称自堆顶至叶子的调整过程为“筛选”。
堆排序(约定进行增排序,因而采用大根堆)
如果初始序列是堆,则可通过反复执行如下操作而最终得到一个有序序列:
筛选过程即输出根:即将根(第一个元素)与当前子序列中的最后一个元素交换。
调整堆:将输出根之后的子序列调整为堆。
如果初始序列不是堆,则首先要将其先建成堆,然后再按(1)的方式来实现。
假设我有一个大根堆,然后我选出根节点,然后将其与最后一个结点交换值,接着让最后一个点回到该呆的位置,做法是不断与三角中的最大值结点交换值,直到稳定
void sift(elementtype A[ ], int k, int n)
//对数组中下标为1~n中的元素中以k为根的子序列调整
{ x=A[k]; finished=FALSE;
i=k; j=2*i; //i指示空位,j先指向左孩子结点
while ( j <= n && !finished )
{ if ( j < n && A[j].key<A[j+1].key) j=j+1;
//让j指向左右孩子中的最大者
if (x.key>=A[j].key) finished=TRUE; //根最大为结束标记
else { A[i]=A[j]; //大的孩子结点值上移
i=j; j=2*j; } //继续往下筛选
}
A[i]=x;
}
构建堆
从一个无序序列建堆的过程就是一个反复“筛选”的过程。若将此序列看成是一个完全二叉树,则最后一个非终端结点是第⌞n/2⌟个元素,由此“筛选”只需从第⌞n/2⌟个元素开始。
通过反复调用筛选操作来实现。
建堆过程要从下往上逐棵子树地进行筛选,即根的下标按照从n/2到1的次序将各子树调整为堆。
先不考虑顺序,按序号构建完全二叉树,然后从倒数第二层开始让每个节点回到它该去的位置(他比他的孩子都大或者是叶子),然后继续上一层,让他们也回到该去的位置,直到根节点也回到了他该去的位置,就好了。
void heap_sort(elementtype A[ ],int n)
{ for (i=n/2; i>=1, i--)
sift(A,i,n); //建初始堆
for (i=n; i>=2; i--)
{ A[i]<==>A[1]; //输出根
sift(A,1,i-1); //调整子序列为堆
}
}
归并排序
归并 基本思想:
是指将两个或两个以上的有序表合并成一个新的有序表。
归并排序:
利用归并的思想进行排序:
首先将整个表看成是n个有序子表,每个子表的长度为1;
然后两两归并,得到n/2个长度为2的有序子表;
然后再两两归并,直至得到一个长度为n的有序表为止。
void Merge(int* arr, int left, int mid, int right){
int *temp = new int[right - left + 1]; //申请一个空间来存放合并好的数组(后面需要销毁)
int st1 = left; //这里是数组1的开始位置
int st2 = mid + 1; //这里是数组2的开始位置
int t = 0; //合并数组的开始位置
while (st1 <= mid && st2 <= right){ //这里结束的条件是一个数组已经放进去完了
//从开始位置比较,如果数组1的元素大于数组2,则将数组2的元素存进去一个,然后位置+1,否则相反
temp[t++] = arr[st1] < arr[st2] ? arr[st1++] : arr[st2++];
}
while (st1 <= mid){ //如果是st1没有放完就直接放在最后面
temp[t++] = arr[st1++];
}
while (st2 <= right){ //如果是st2没有放完就直接放在最后面
temp[t++] = arr[st2++];
}
for (int j = 0; j < t; ++j){ //这里把临时创建的数组拷贝到原来的数组中
arr[left + j] = temp[j];
}
delete[] temp; //销毁临时变量
}
void MergeSort(int* arr, int start, int end){
if (arr == NULL || start >= end)
return;
if (start < end)
{
int mid = (start + end) / 2; //找到每个分块的中间值
MergeSort(arr, start, mid); //左边递归进行分离和合并
MergeSort(arr, mid + 1, end); //右边递归进行分离和合并
Merge(arr, start, mid, end); //左右合并
}
}
————————————————
版权声明:本文为CSDN博主「不凡Zzz」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_36791466/article/details/99974205
多关键字与基数排序
先按顺序放到各位桶中,在按0-9下到上的顺序将数字放到十位桶中,再放到百位桶中,莫名其妙地就排好了。