选择排序基本思想:每一趟(如第i趟)在后面n-i+1个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到n-1趟昨晚,待排序元素只剩下1个,就不用再选了。
人话:有一堆数,以升序为例子,第一次从这些数中找到最小的放在第一个位置,第二次从剩下的中找到最小的放在之前排好数后面,以此类推,直到待排序的数一个也没了。
简记:每次找最小,挨着放
目录
一、简单选择排序
(又名直接选择排序)基本来自选择排序的思想,
基本步骤:(1)选出最小的
(2)若它不是第一个,则与第一个交换位置
(3)去除之前选出的最小与那素中,继续执行(1)(2),直到剩余元素只有一个为止
执行流程:
初始序列{49,38,65,97,76,13,27}
在排序过程中,把序列分为有序部分和无序部分。开始时,整个序列为无序序列。
无序 | ||||||
49 | 38 | 65 | 97 | 76 | 13 | 27 |
第一趟,从无序序列选出最小元素13,它与无序序列中第一个元素交换
有序 | 无序 | |||||
13 | 38 | 65 | 97 | 76 | 49 | 27 |
第二趟,从无序序列选出最小元素27,它与无序序列中第一个元素交换
有序 | 无序 | |||||
13 | 27 | 65 | 97 | 76 | 49 | 38 |
重复上述操作,直到无需序列没有元素
性能:空间效率:仅使用常数个辅助单元,为O(1)
时间效率:为
稳定性:在第i趟找到最小元素后,和第i个元素交换,会导致第i个元素与其含有相同关键字元素的相对位置发生改变。
所以不稳定
#include<stdio.h>
#include<stdlib.h>
void swap(int *a,int *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void SelectSort(int arr[],int n)
{ //arr进行排序,arr从0开始存放元素
int i,j;
for(i=0;i<n-1;i++){//一共进行n-1趟排序
int min=i;
for(j=i+1;j<n;j++) //再arr[i..n-1]中选择最小元素
if(arr[j]<arr[min]) min=j;//更新最小元素位置
if(min!=i)
swap(&arr[i],&arr[min]); //与第i个交换位置
}
}
void put(int arr[],int n){
int j;
for(j=0;j<n;j++)
printf("%d\t",arr[j]);
printf("\n");
}
void main(){
int array[]={0,4,2,1,4,2,8,3,1,10};
int i;
int len=sizeof(array)/sizeof(array[0]);
printf(" \n排序前\n");
put(array,len);
SelectSort(array,len);
printf(" \n排序后\n");
put(array,len);
}
二、堆排序⭐⭐⭐⭐
利用堆及其运算,可实现选择排序。
堆排序步骤为:(1)根据初始输入数据,利用堆的调整算法,形成初始堆
(2)通过一系列的元素交换和重新调整堆进行排序
具体为:把大根堆对顶的最大数取出,将剩余的堆继续调整为大根堆,再次将堆顶的最大数取出,这个过程持续 到剩余数只有一个时结束
为了实现元素从小达到排序,所以要建立大根堆。(小根堆与之类似)
大根堆的定义:
-
大根堆中的最大元素值出现在根结点(堆顶)
-
堆中每个父节点的元素值都大于等于其孩子结点
执行流程:
(1)建堆,先将序列调整为一个大顶堆。原始序列对应的完全二叉树如图
在这个完全二叉树中,结点76、13、27、49* 是叶子结点,它们没有左孩子,所以它们满足堆的定义。
从97开始,按87、65、48、49的顺序依次 调整。
1.建堆
(1)调整97。97>49*,所以97和它的孩子49*满足堆的定义,所以不需要调整
(2)调整65。65>13,65>27,所以65和它的孩子13、27满足堆的定义,不需要调整。
(3)调整38。38<97,38<76,不满足堆定义,需要调整。在这里38的两个孩子都比38大,应该选择更大的97交换。因为,若和76交换,则76<97仍然不满足堆的定义。
因此,将38和97交换。交换后38成了49*的根结点,38<49*,仍然不满足堆定义,需要继续调整,将48和49*交换得到如图所示
(4)调整49.49<97,49<65不满足堆的定义,需要调整,找到较大的孩子97,将49和97交换。
交换后49<76仍不满足堆定义,继续调整,将49与76交换,得到如图
2.插入结点
3.删除结点
4.排序
此时已经建立好一个大根堆,对应的序列为97 ,76 ,65 , 49* ,49 ,13 ,27 ,38
将堆顶关键字97和序列最后一个关键字38交换。第一趟排序完成,97到达最终位置。
将除97外的序列38,76,65,49*,13,27,重新调整为大根堆。
现在只有38不满足堆定义,其他的关键字都满足,只需调整38,结果如图
现在序列为 76 ,49*,65,38,49,13,27.将堆顶关键字76和最后一个关键字27交换,第二趟排序完成。
76到达其最终位置,此时序列为 27 49* 65 38 46 13 76 97
堆除去76和977的序列依照上述方法进行处理,直到树中只有一个结点时完成。
性能分析:时间复杂度 平均为
空间复杂度 O(1)
稳定性:不稳定
#include <stdio.h>
#include <stdlib.h>
void swap(int *a,int *b)
{
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
void max_heapify(int arr[], int start, int end)
{
//建立父节点指标和子节点指标
int dad = start;
int son = dad * 2 + 1;
while (son <= end) //若子节点指标在范围内才做比较
{
if (son + 1 <= end && arr[son] < arr[son + 1])
//先比较两个子节点大小,选择最大的
son++;
if (arr[dad] > arr[son]) //如果父节点大於子节点代表调整完毕,直接跳出函数
return;
else //否则交换父子内容再继续子节点和孙节点比较
{
swap(&arr[dad], &arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len)
{
int i;
//初始化,i从最後一个父节点开始调整
for (i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先将第一个元素和已排好元素前一位做交换,再重新调整,直到排序完毕
for (i = len - 1; i > 0; i--)
{
swap(&arr[0], &arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
int i;
for (i = 0; i < len; i++)
printf("%d ", arr[i]);
printf("\n");
return 0;
}
三 、锦标赛排序
锦标赛排序,也称为树形选择排序(Tree Selection Sort),是一种按照锦标赛的思想进行选择排序的方法。
首先对n个记录进行两两比较,然后优胜者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。这个过程可 以用一棵有n个叶子结点的完全二叉树表示。根节点中的关键字即为叶子结点中的最小关键字。在输出最小关键字之后,根据关系的可传递性,欲选出次小关键字,
仅需将叶子结点中的最小关键字改为“最大值”,如∞,然后从该叶子结点开始,和其左(右)兄弟的关键字进行比较,修改从叶子结点到根的路径上各结点的关键 字,则根结点的关键字即为次小关键字。
这种算法的缺点在于:辅助存储空间较多、最大值进行多余的比较。