选择排序
基本思想
每一趟在后面n-i+1个待排序的元素中找到最小的然后放置在第i个位置,就是位置0-length-1下标相当于是已经知道顺序的了,然后在序列里找每组序列的最小元素放在这个已知位置就可以了。
简单选择排序
基本思想
每一趟排序记录当前元素的最终位置,设置一个变量,每当要交换的时候,随时更改变量的值
#include <iostream>
using namespace std;
void EasyChoiceSort(int *Array,int length);
int main ()
{
int Array[11]={0,2,4,1,6,3,7,8,9,5,1};
int length=10;
EasyChoiceSort(Array,length);
cout<<"Result1:";
for(int i=1;i<=10;i++)
cout<<Array[i]<<" ";
cout<<endl;
return 0;
}
void EasyChoiceSort(int *Array,int length)
{
int location;
for(int i=1;i<=length;i++)
{
location=i;
for(int j=i+1;j<=length;j++)
{
if(Array[j]<Array[location])
location=j;
}
//一开始没加这句,其实无形之中提升了效率,如果位置都相同的话,就不用再交换一次了
if(location!=i)
{
Array[0]=Array[location];
Array[location]=Array[i];
Array[i]=Array[0];
}
}
}
简单排序算法性能分析
性能 | 分析 |
---|---|
空间 | O(1) |
时间 | 简单排序的过程中,元素移动的操作次数很少,不会超过3(n-1)次,最好的情况是移动0次,此时对应的表已经有序;但元素间比较的次数与序列的初始状态无关,始终是n(n-1)/2次,所以时间复杂度为O(n2) |
稳定性 | 不稳定 |
适用性 | 顺序表,但是如果给链表其实也可以 |
堆排序
堆排序特点
在排序过程中,将整个顺序表看成一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的元素。
堆的定义
大根堆
最大元素存放在根结点中,且对任意非根结点,它的值小于或等于其双亲结点
小根堆
最小元素存放在根结点中,且对任意非根结点,它的值大于或等于其双亲结点
相关算法思路
- 构建初始堆
初始建堆的过程就是一个不断筛选的过程。假设该序列长度为n,最后一个元素应当是n/2的子节点,对以n/2为根的子树筛选,若根结点小于左右子结点的最大值,则交换,使该子树称为堆。然后依次向前n/2~1为根的子树进行筛选,看结点值是否大于左右子结点的最大值,若不是交换,但是交换后可能会破坏下一级的堆,于是继续采用上述方法构造下一级的堆,直到以该结点为根的子树重新成为堆为止。反复利用上述调整堆的方法建堆,知道根结点为止。 - 堆排序
首先将存放在数组中的n个元素建成初始堆,由于堆本身的特点,堆顶元素就是最大值。堆顶元素输出之后,将堆底元素送入堆顶,此时根结点已不满足大根堆的性质,堆被破坏,将堆顶元素向下调整使其继续保持大根堆的性质,再输出堆顶元素,反复至堆中只剩下一个元素为止。
用了将堆顶元素和最后一个元素交换,然后每次进行调整的范围都减小1. - 堆的删除和插入
堆的删除只能在堆顶进行,将堆顶元素和最后一个元素交换,然后再将新的堆顶元素进行向下调整AdjustDown()
堆的插入先将新的结点放在最后,然后再对这个结点进行向上调整AdjustUp() - 向上调整堆
不断向上调整和父节点进行比较,结点值大于双亲结点,将双亲结点下调,并继续向上比较
#include <iostream>
using namespace std;
void BuildMaxHeap(int *Array,int length);
void AdjustDown(int *Array,int k,int length);
void HeapSort(int *Array,int length);
void AdjustUp(int *Array,int length);
void DeleteElement(int *Array,int length);
void InsertElement(int *Array,int length,int key);
int main ()
{
int Array[9]={0,53,17,78,9,45,65,87,32};
int length=8;
BuildMaxHeap(Array, length);
cout<<"ResultInitial:";
for(int i=1;i<=8;i++)
cout<<Array[i]<<" ";
cout<<endl;
DeleteElement(Array, length);
cout<<"ResultDelete:";
for(int i=1;i<=7;i++)
cout<<Array[i]<<" ";
cout<<endl;
/* HeapSort(Array, length);
cout<<"Result1:";
for(int i=1;i<=8;i++)
cout<<Array[i]<<" ";
cout<<endl;*/
return 0;
}
//初始建堆
void BuildMaxHeap(int *Array,int length)
{
//遍历整个数组只需要从n/2往前,利用完全二叉树的性质就可以对整个数组进行排列
for(int i=length/2;i>0;i--)
{
AdjustDown(Array,i, length);
}
}
//先从宏观上看不要陷入算法的细节
//函数AdjustDown是为了找到k的最终位置,而在寻找的过程中把中间不恰当的位置都掉换了
void AdjustDown(int *Array,int k,int length)
{
Array[0]=Array[k];
for(int i=2*k;i<=length;i=i*2)//注意这里for循环条件的理解
{
//先对两个子节点的大小进行判断,选择出二者中最合适的
if(Array[i]<Array[i+1]&&i<length)
++i;
//将选择出来的与标准值进行比较
if(Array[i]<Array[0])
break;
else{//将发生交换的进行记录,就知道可能会发生破坏的下一级的堆的位置,不发生交换的当然不会发生破坏,就直接记录这个发生交换的就行
Array[k]=Array[i];
k=i;//当发现不平衡并对当前一层调整完之后,更新为根继续,i和k是相等的,这样利用i乘2之后才能得到下一个的子节点
}
}
Array[k]=Array[0];//找到最终k的位置
}
//堆排序算法
//第一个位置排列的一定是最大的元素,利用最后一个元素与第一个元素交换,然后向下调整
//这样最后一个位置放置的就是整个序列中最大的元素,然后依次递减
void HeapSort(int *Array,int length)
{
BuildMaxHeap(Array, length);
for(int i=length;i>0;i--)
{
Array[0]=Array[i];
Array[i]=Array[1];
Array[1]=Array[0];
//每次交换之后都要把当前的堆进行重新调整
AdjustDown(Array, 1, i-1);//注意最后一个数的范围是i-1,因为i的位置已经放置上合适的值了,得在前面的那些数中进行寻找
}
}
//堆中元素删除算法
void DeleteElement(int *Array,int length)
{
Array[1]=Array[length];
AdjustDown(Array,1, length);
//交换之后实际上是在最后一个位置,那么就得让数组的实际长度发生变化来体现那个数被删除了
//直接length--没用,传参
//length--;
}
//堆中元素插入操作
void InsertElement(int *Array,int length,int key)
{
Array[length+1]=key;//注意不要越界,元素的范围
AdjustUp(Array, length+1);
//length++;
}
//向上调整堆的算法
void AdjustUp(int *Array,int length)
{
Array[0]=Array[length];//0结点为一个中间值结点
int i=length/2;//从上往下是乘2,从下往上是/2操作
while(i>0&&Array[i]<Array[0])
{
Array[length]=Array[i];
length=i;
i=length/2;
}
Array[length]=Array[0];
}
//明确当前操作的是哪个结点,真正对结果有影响的是哪个结点,不要被其他部分干扰
堆排序算法性能分析
性能 | 分析 |
---|---|
时间 | 建堆时间为O(n),之后有n-1次向下调整操作,每次调整的时间复杂度为O(h)【树的高度】,在最好、最坏、平均情况下时间复杂度为O(nlog2n) |
空间 | O(1) |
稳定性 | 不稳定 |
适用性 | 利用了完全二叉树的性质,用顺序表存储 |