<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">选择排序的基本运算都是在n个元素组成的序列中,选择一个关键字最大或最小的元素输出,然后再从剩余的n-1个元素中选择一个关键字最大或最小的元素输出,以此类推,直到排序结束。</span>
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">以递增排序为例,<span style="color:#ff0000;">简单选择排序</span>过程如下:1第一次在数组中查找最小值a[i],然后将a[i]和a[0]交换位置。</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">2从a[1]开始,同样从a[1]开始往后找到最小值a[j],然后与a[1]交换位置,依次类推。</span><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">废话不多说,直接贴代码</span>
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
//**************5简单选择排序************************ //比冒泡法交换的次数少,比较多,交换少 void SelectSort(int a[],int len) { int i,j,min,temp; for(i=0;i<len-1;i++) { min=i; for(j=i+1;j<len;j++) if(a[j]<a[min]) min=j; temp=a[i]; a[i]=a[min]; a[min]=temp; } }
简单选择排序 时间复杂度O(n^2)
稳定性分析:选择排序是给每个位置选择当前元素最小(或最大)的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,因为只剩下它一个最大的元素了。那么,在一趟选择,如果当前元素比一个元素小,而该小的元素又出现在一个和当前元素相等的元素后面,那么
交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 我们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,所以选择排序是一个不稳定的排序算法。
简单选择排序的时间主要浪费在对于n个元素(乱序)如何----在小于O(n)时间内查找最大值或最小值
改进:堆排序
堆是一棵完全二叉树,其中任一非叶子节点的关键字均大于(或小于)等于其孩子节点的关键字。分为最大堆和最小堆
用最大堆进行升序排序:先构造初始最大堆,由于最大堆的第一个关键字就是最大值,直接将第一个关键字和最后一个关键字交换(是不是和简单选择排序很相似),然后再调整其余关键字,使其重新构成最大堆,然后再将第一个关键字与当前最大堆的最后一个关键字交换,依次类推,直到排序完成。
简而言之就是: 构造初始堆---->取堆顶------>调整剩余堆------>取剩余堆顶------->.........-------->空堆end
构造初始堆的过程描述如下:
废话不多说,直接上代码:
//*****************6堆排序******************************* //升序排序构造大顶堆,每次把最大元素(根)放在数组最后一个位置 //对于剩余元素重新构造二叉堆-----以数组形式存储堆 (完全二叉树) void HeapAdjust(int a[], int i, int size) { //调整堆(大小为size--a[0~size])的第i个节点使之为大顶堆 size=len-1;节点编号0,1,...size. int lchild = 2*i+1;//i的左孩子节点序号 int rchild = 2*i+2;//i的右孩子节点序号 int max=i; int temp; if(i<=size/2)//如果i是叶节点就不用进行调整 { if(lchild<=size && a[lchild]>a[max]) max=lchild; if(rchild<=size && a[rchild]>a[max]) max=rchild; if(max!=i) { temp=a[i]; a[i]=a[max]; a[max]=temp; //避免调整之后以man为父节点的子树不是大顶堆 HeapAdjust(a,max,size); } } } void HeapSort(int a[],int size) { //HeapSort(a,9);size=n-1;不是元素个数 int i,temp; for(i=size/2;i>=0;i--)//建立初始堆 非叶子节点最大序号值为size/2 HeapAdjust(a,i,size); for(i=size;i>=0;i--) { temp=a[0]; //交换堆顶和最后一个元素 a[0]=a[i]; a[i]=temp; //BuildHeap(a[],i-1);//将余下元素重新建立为大顶堆 HeapAdjust(a,0,i-1);//重新调整堆顶节点成为大顶堆 } }
由于堆是完全二叉树,其实还是用数组来描述堆。。。。。
下面对代码进行解释:
HeapSort第一个for循环就是建立堆的过程,从第一个非叶子节点开始进行调整堆(HeapAdjust)最后一个个非叶子节点可能是size/2,可有可能是size/2-1,至于为什么从编号为size/2开始,是为了保证最大可能性,进一步的确定是不是叶子节点可在HeapSort中的if(lchild<=size)等中进行判断。
HeapSort第二个for循环就是取堆顶,调整堆的过程。
下面分析时间复杂度(参考算法导论)
分析调整堆的时间复杂度
重调堆,时间复杂度为T(lgn)
堆排序的时间复杂度T(nlgn) 无论最好最坏都是一样的