选择排序:每一趟从待排序列中选择最小的元素作为有序子序列中的元素,待元素只剩下一个,就不用选了。
一,简单选择排序
1.过程:假设以A[]表示数组
1.1最开始定义一个变量用来存储数组数组第一个元素的序号 i = 0; min = i,A[min] = 49;
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
待排序列 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |
1.2用min对应数组中的元素依次往后作比较,A[min] = 49 > A[1] = 38,所以此时min = 1,A[min] =38
然后继续向后进行比较,A[min] = 38 < A[2] = 65; A[min] = 38 < A[3] = 97; A[min] = 38 < A[4]=76
A[min] = 38 > A[5] = 13,所以令 min = 5,A[min] = 13; A[min] = 13 < A[6] = 27; A[min] = 13<A[7]=49
此时序号min就是数组中最小的元素,因此交换位置A[min]和A[i];
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
待排序列 | 13 | 38 | 65 | 97 | 76 | 49 | 27 | 49 |
2.代码展示
//简单选择排序
void SelectSort(Str &L)
{
for(int i = 0; i < L.length-1; ++i)//一共进行n-1趟
{
int min = i;//记录最小元素的位置
for(int j = i+1; j < L.length; ++j)//在A[i...n-1]中选择最小的元素
if(L.data[j] < L.data[min])
min = j;//更新最小元素的位置
if(min != i)
swap(L.data[min],L.data[i]);//在swap函数中交换元素位置,函数中元素共移动了3次
}
}
3.结果:
4.分析
空间复杂度:仅使用了常数个辅助单元,空间复杂度为O(1)
时间复杂度:在简单选择排序过程中,元素移动的操作次数很少,不会超过3(n-1)(假如是n个元素的逆序,故n-1个都是需要移动,在swap函数中,每次交换元素位置的时候移动3次,故不会超过3(n-1)) ;元素之间的比较次数与序列的初始状态无关,始终是n(n-1)/2次(每次都是从待排序列的第一个元素开始往后进行比较n-1+....+1),所以时间复杂度为O()。
二,堆排序
堆的定义:n个关键字序列L[1...n]称为堆,当且仅当该序列满足:
(1)L(i) >= L(2i) 且L(i) >= L(2i+1)或(2)L(i) <= L(2i)且L(i) <= L(2i+1)。可以将堆视为一棵完全二叉树,满足条件(1)的堆称为大根堆;满足条件(2)的堆称为小根堆。
1.过程
1.1创建堆的过程(以大根堆为例)
数组序号 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
待排序列 | 49 | 38 | 65 | 97 | 76 | 13 | 27 | 49 |
从左到右,从上到下,图的编号是1,2,3,4,5;
创建大根堆的步骤:
1.第1张图是待排序列按照完全二叉树的形式进行排版。
2.从完全二叉树的最后一个非终端结点开始,依次向上,与其左右孩子结点的最大值进行比较。
3.因为97只有左孩子且其值为49 ,97>49,所以不进行交换;因为13<27,所以65与其右孩子的值17进行比较,65>27,所以不进行交换;因为97>76,所以38与其左孩子的值97进行比较,38<97,所以进行交换,交换后如第2张图; 因为97>65, 所以49与其左孩子的值97进行比较,49<97,交换后如第3张图。
4.然后按照第3步进行检查,因为值为38的结点只有左孩子,所以38与其左孩子值为49进行比较,38<49,所以进行交换,交换后如第4张图;因为49<76,所以49与其右孩子值为76进行比较,49<76,所以进行交换,如第5张图。如此完成该例子的大根堆创建,结果就是第5张图。
创建大根堆的代码展示:
//以大根堆的形式调整元素所在位置
void MaxHeadAdjust(Str &L, int k)
{//将元素k为根的子树进行调整
L.data[0] = L.data[k];//最开始将根结点元素放在序号为0的位置
for(int i = 2*k; i <= L.length; i*=2)//沿着较大的子结点向下筛选
{
if(i < L.length && L.data[i] < L.data[i+1])//当i < 待排序列的长度的时候才会,关键字才会有右孩子;如果左孩子的值小于右孩子的值,那么关键字就和右孩子的值进行比较
i++;//i++就表示右孩子的序号
if(L.data[0] >= L.data[i])//如果关键字大于等于孩子结点的值,那么就不需要进行交换
break;//直接结束程序,也不需要再次下坠
else
{
L.data[k] = L.data[i];//如果小于,那么就将孩子的值赋值到双亲结点
k = i;//那么随之而来就是将关键字的序号进行改变
}
}
L.data[k] = L.data[0];//确定最后位置之后就将原本存储到0序号的元素的值赋值到最终位置
}
//创建大根堆
void BuildMaxHeap(Str &L)
{
for(int i = L.length/2; i >= 1; --i)//从i = [n/2] -- 1,反复调整堆
MaxHeadAdjust(L,i);
}
1.2堆排序的过程(以大根堆为例)
堆排序:每一趟将堆顶元素加入有序子序列(与创建堆后的待排序列的最后一个元素交换),并且将序列长度降低1,然后现在的待排序列再次调整成大根堆。(小元素不断”下坠“)。
1.第一张图是创建堆的时候的完全二叉树,然后将堆顶元素与最后一个元素进行交换,输出堆顶元素,然后减少待排序列的长度。
2.因为76>63,所以将元素38"下坠",和76交换位置;得到图3,后续是同样步骤得到图4.
堆排序代码展示:
//大根堆堆排序算法
void MaxHeapSort(Str &L)
{
BuildMaxHeap(L);
for(int i = L.length; i >= 1; --i)
{
printf("%d ",L.data[1]);
swap(L.data[1],L.data[i]);
L.length--;
MaxHeadAdjust(L,1);
}
printf("\n");
}
2.插入元素
基本思想:将元素插入到待排序列的后面一个位置,对于大根堆,向上比较双亲结点,构成符合大根堆的样子
代码展示:
//大根堆堆排序插入
void InsertMaxHeap(Str &L, int v)
{
int i = ++L.length;//插入元素则数组的有效长度增加1
int j = i/2;//双亲结点
L.data[L.length] = v;//将值赋值给数组中
while(L.data[i] > L.data[j])//进行比较
{
swap(L.data[i],L.data[j]);//大于则交换位置
i = j;//继续向上比较
j = i/2;
}
}
3.删除元素
基本思想:将删除的元素用数组最后一个元素进行代替,然后再构造成大根堆
//大根堆堆排序删除
void DeletMaxHeap(Str &L, int &w)
{
int i;
printf("请输入想要删除数组元素的序号:\n");
scanf_s("%d",&i);
w = L.data[i];
swap(L.data[L.length],L.data[i]);
L.length--;
MaxHeadAdjust(L,i);
}
4.小根堆(过程和大根堆类似,改变符号就好了,此处就展示代码)
4.1创建小根堆
//以小根堆的形式调整数组中的元素
void MinHeadAdjust(Str &L, int k)
{
L.data[0] = L.data[k];
for(int i = 2*k; i <= L.length; i*=2)
{
if(i < L.length && L.data[i] > L.data[i+1])
i++;
if(L.data[0] <= L.data[i])
break;
else
{
L.data[k] = L.data[i];
k = i;
}
}
L.data[k] = L.data[0];
}
//创建小根堆
void BuildMinHeap(Str &L)
{
for(int i = L.length/2; i >= 1; --i)
MinHeadAdjust(L,i);
}
4.2堆排序
//小根堆堆排序
void MinHeapSort(Str &L)
{
BuildMinHeap(L);
for(int i = L.length; i >= 1; --i)
{
printf("%d ",L.data[1]);
swap(L.data[1],L.data[i]);
L.length--;
MinHeadAdjust(L,1);
}
printf("\n");
}
4.3插入元素
//小根堆堆排序插入
void InsertMinHeap(Str &L, int v)
{
int i = ++L.length;
int j = i/2;
L.data[L.length] = v;
while(L.data[i] < L.data[j])
{
swap(L.data[i],L.data[j]);
i = j;
j = i/2;
}
}
4.4删除元素
//小根堆堆排序删除
void DeletMinHeap(Str &L, int &w)
{
int i;
printf("请输入想要删除数组元素的序号:\n");
scanf_s("%d",&i);
w = L.data[i];
swap(L.data[L.length],L.data[i]);
L.length--;
MinHeadAdjust(L,i);
}
结果:
5.分析
在建含n个元素的堆时,关键字的比较次数不会超过4n,时间复杂度为O(n)
空间复杂度:仅使用了常数个辅助单元1,所以空间复杂度为O(1)
时间复杂度:建堆时间为O(n),之后有n-1次向下调整,每次调整时间的复杂度为O(h),h = 为树高,故在最好最坏的平均条件下,堆排序的时间复杂度为O(n)+O(n) = O(n)
三,总结:
选择排序 | 空间复杂度 | 时间复杂度 | 稳定性 |
简单选择排序 | O(1) | O() | 不稳定 |
堆排序 | O(1) | O(n) | 不稳定 |
四,完整代码
#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
//定义数组结构体
typedef struct
{
int *data;
int length;
}Str;
//函数说明
void CreatString(Str &L);
void CreatString1(Str &L);
void swap(int &a, int &b);
void SelectSort(Str &L);
void PrintStr(Str L);
void PrintStr1(Str L);
//大根堆
void BuildMaxHeap(Str &L);
void MaxHeadAdjust(Str &L, int k);
void MaxHeapSort(Str &L);
void InsertMaxHeap(Str &L, int v);
void DeletMaxHeap(Str &L, int &w);
//小根堆
void BuildMinHeap(Str &L);
void MinHeapSort(Str &L);
void MinHeadAdjust(Str &L, int k);
void InsertMinHeap(Str &L, int v);
void DeletMinHeap(Str &L, int &w);
int main(void)
{
Str L;
CreatString(L);
SelectSort(L);
printf("简单选择排序之后数组元素为:\n");
PrintStr(L);
//大根堆
Str L1;
CreatString1(L1);
printf("大根堆堆排序之后数组元素为:\n");
MaxHeapSort(L1);//堆排序之后元素是有序的
Str L2;
CreatString1(L2);
BuildMaxHeap(L2);
printf("创建大根堆之后数组元素为:\n");
PrintStr1(L2);//创建完堆之后输出数组中的元素,数组中元素不一定是有序的
printf("请输入你想插入的元素:\n");
int v;
scanf_s("%d",&v);
InsertMaxHeap(L2,v);
printf("插入元素之后数组元素为:\n");
PrintStr1(L2);
Str L2_1 = L2;
printf("堆排序之后数组元素为:\n");
MaxHeapSort(L2);
int w;
DeletMaxHeap(L2_1,w);
printf("删除元素%d之后数组元素为:\n",w);
PrintStr1(L2_1);
printf("删除元素之后堆排序为:\n");
MaxHeapSort(L2_1);
//小根堆
Str L3;
CreatString1(L3);
printf("小根堆堆排序之后数组元素为:\n");
MinHeapSort(L3);//堆排序之后元素是有序的
Str L4;
CreatString1(L4);
BuildMinHeap(L4);
printf("创建小根堆之后数组元素为:\n");
PrintStr1(L4);//创建完堆之后输出数组中的元素,数组中元素不一定是有序的
printf("请输入你想插入的元素:\n");
int x;
scanf_s("%d",&x);
InsertMinHeap(L4,x);
printf("插入元素之后数组元素为:\n");
PrintStr1(L4);
Str L4_1 = L4;
int y;
DeletMinHeap(L4_1,y);
printf("删除元素%d之后数组元素为:\n",y);
PrintStr1(L4_1);
printf("小根堆堆排序之后元素为:\n");
MinHeapSort(L4_1);
return 0;
}
//创建数组
void CreatString(Str &L)
{
L.data = (int *)malloc(sizeof(int)*8);
L.length = 8;
int val;
for(int i = 0; i < L.length; ++i)
{
printf("请输入第%d个元素的值:",i+1);
scanf_s("%d",&val);
L.data[i] = val;
}
}
void CreatString1(Str &L)
{
L.data = (int *)malloc(sizeof(int)*12);
L.length = 8;
int val;
for(int i = 1; i <= L.length; ++i)
{
printf("请输入第%d个元素的值:",i);
scanf_s("%d",&val);
L.data[i] = val;
}
}
//简单选择排序
void SelectSort(Str &L)
{
for(int i = 0; i < L.length-1; ++i)//一共进行n-1趟
{
int min = i;//记录最小元素的位置
for(int j = i+1; j < L.length; ++j)//在A[i...n-1]中选择最小的元素
if(L.data[j] < L.data[min])
min = j;//更新最小元素的位置
if(min != i)
swap(L.data[min],L.data[i]);//在swap函数中交换元素位置,函数中元素共移动了3次
}
}
//元素交换位置
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
//遍历输出
void PrintStr(Str L)
{
for(int i = 0; i < L.length; ++i)
{
printf("%d ",L.data[i]);
}
printf("\n");
}
void PrintStr1(Str L)
{
for(int i = 1; i <= L.length; ++i)
{
printf("%d ",L.data[i]);
}
printf("\n");
}
//以大根堆的形式调整元素所在位置
void MaxHeadAdjust(Str &L, int k)
{//将元素k为根的子树进行调整
L.data[0] = L.data[k];//最开始将根结点元素放在序号为0的位置
for(int i = 2*k; i <= L.length; i*=2)//沿着较大的子结点向下筛选
{
if(i < L.length && L.data[i] < L.data[i+1])//当i < 待排序列的长度的时候才会,关键字才会有右孩子;如果左孩子的值小于右孩子的值,那么关键字就和右孩子的值进行比较
i++;//i++就表示右孩子的序号
if(L.data[0] >= L.data[i])//如果关键字大于等于孩子结点的值,那么就不需要进行交换
break;//直接结束程序,也不需要再次下坠
else
{
L.data[k] = L.data[i];//如果小于,那么就将孩子的值赋值到双亲结点
k = i;//那么随之而来就是将关键字的序号进行改变
}
}
L.data[k] = L.data[0];//确定最后位置之后就将原本存储到0序号的元素的值赋值到最终位置
}
//创建大根堆
void BuildMaxHeap(Str &L)
{
for(int i = L.length/2; i >= 1; --i)//从i = [n/2] -- 1,反复调整堆
MaxHeadAdjust(L,i);
}
//大根堆堆排序算法
void MaxHeapSort(Str &L)
{
BuildMaxHeap(L);
for(int i = L.length; i >= 1; --i)
{
printf("%d ",L.data[1]);
swap(L.data[1],L.data[i]);
L.length--;
MaxHeadAdjust(L,1);
}
printf("\n");
}
//大根堆堆排序插入
void InsertMaxHeap(Str &L, int v)
{
int i = ++L.length;//插入元素则数组的有效长度增加1
int j = i/2;//双亲结点
L.data[L.length] = v;//将值赋值给数组中
while(L.data[i] > L.data[j])//进行比较
{
swap(L.data[i],L.data[j]);//大于则交换位置
i = j;//继续向上比较
j = i/2;
}
}
//大根堆堆排序删除
void DeletMaxHeap(Str &L, int &w)
{
int i;
printf("请输入想要删除数组元素的序号:\n");
scanf_s("%d",&i);
w = L.data[i];
swap(L.data[L.length],L.data[i]);
L.length--;
MaxHeadAdjust(L,i);
}
//以小根堆的形式调整数组中的元素
void MinHeadAdjust(Str &L, int k)
{
L.data[0] = L.data[k];
for(int i = 2*k; i <= L.length; i*=2)
{
if(i < L.length && L.data[i] > L.data[i+1])
i++;
if(L.data[0] <= L.data[i])
break;
else
{
L.data[k] = L.data[i];
k = i;
}
}
L.data[k] = L.data[0];
}
//创建小根堆
void BuildMinHeap(Str &L)
{
for(int i = L.length/2; i >= 1; --i)
MinHeadAdjust(L,i);
}
//小根堆堆排序
void MinHeapSort(Str &L)
{
BuildMinHeap(L);
for(int i = L.length; i >= 1; --i)
{
printf("%d ",L.data[1]);
swap(L.data[1],L.data[i]);
L.length--;
MinHeadAdjust(L,1);
}
printf("\n");
}
//小根堆堆排序插入
void InsertMinHeap(Str &L, int v)
{
int i = ++L.length;
int j = i/2;
L.data[L.length] = v;
while(L.data[i] < L.data[j])
{
swap(L.data[i],L.data[j]);
i = j;
j = i/2;
}
}
//小根堆堆排序删除
void DeletMinHeap(Str &L, int &w)
{
int i;
printf("请输入想要删除数组元素的序号:\n");
scanf_s("%d",&i);
w = L.data[i];
swap(L.data[L.length],L.data[i]);
L.length--;
MinHeadAdjust(L,i);
}