排序算法

来源:互联网 发布:浏览器广告拦截软件 编辑:程序博客网 时间:2024/06/07 03:58

本来以前对各种排序很熟悉的,不过好多年没弄了,陌生了。在此再过一遍,熟悉熟悉。
1、快排,不稳定。
算法思路:
*每次选一个参考元素key,然后将数组分成两部分,左边的都小于等于key,右边的都大于等于key。

void QuickSort(int *arr, int Begin, int End){assert(arr);if(Begin >= End)return;int i,j,key;//可以随机挑选一个位置,然后swap(arr[rand], arr[Begin]);key = arr[Begin];i = Begin;j = End;while(i < j){while(i < j && arr[j] >= key)//从后往前找到第一个小于key的数--j;if(i < j)arr[i++] = arr[j];while(i < j && arr[i] <= key)//从前往后找到第一个大于key的数++i;if(i < j)arr[j--] = arr[i];}arr[i] = key;QuickSort(arr, Begin, i - 1);QuickSort(arr, i + 1, End);}

对于快排,当然还有另一个版本,将arr的最后一个元素作为key,然后从0的位置开始搜索,用Begin记下已经找到的比key小的元素的最后一个元素+1的位置,也就是第一个比key大的元素的位置。如果当前扫描的j比key小,则与记录的Begin位置的元素交换。最后交换Begin和最后一个元素(也就是key)的值。
参考代码如下:
题目延伸,也就是给一个数组,交换只能与特定的元素交换。见博客:http://blog.csdn.net/kay_zhyu/article/details/11661173
参考代码如下:

void QuickSort(int *arr, int nLen){if(nLen <= 0){return;}assert(arr);int key = arr[nLen - 1];int Begin = 0;for(int j = 0; j < nLen - 1; ++j){if(arr[j] <= key){Swap(arr[j], arr[Begin]);++Begin;}}Swap(arr[Begin], arr[nLen - 1]);QuickSort(arr, Begin);QuickSort(&arr[Begin + 1], nLen - Begin - 1);}
2、归并排序,稳定排序。没用递归的方法,因为那样需要的内存太多,效率低。归并排序的一个扩展就是求逆序对
算法思路:
*先将arr数组分成步长为l的小数据段,l从1到nLen,将保证小数据段中的数据是有序的。初始状态数据段长度为1,一定有序。
*将两个有序的数据段归并成一个有序的数据段。
这里为什么要用一个ans数组,防止每次在归并的时候都要申请内存,这样效率不高。

void Merge(int *arr, int *ans, int Begin, int Mid, int End){//将arr中的元素归并到ans中,两段数据分别是[Begin,Mid),[Mid,End).int i,j,k;for(i = Begin, j = Mid, k = Begin; i < Mid && j < End; ++k){if(arr[i] <= arr[j])ans[k] = arr[i++];elseans[k] = arr[j++];}for( ; i < Mid; ++i, ++k)ans[k] = arr[i];for( ; j < End; ++j, ++k)ans[k] = arr[j];memcpy(&arr[Begin], &ans[Begin], (End - Begin) * sizeof(int));}//对整个arr数组进行归并排序,结果存放在ans之中void MergeSort(int *arr, int *ans, int nLen){int i,l,e;//步长跨度for(l = 1; l < nLen; l *= 2){for(i = 0; i + l < nLen; i += l + l){//第二部分的结束e = i + l + l > nLen ? nLen : i + l + l;Merge(arr, ans, i, i + l, e);}}}

3、堆排序,不稳定排序。用最大堆排序,则得到一个递增的序列;用最小堆排序得到的是一个递减的序列。
算法思路,这里以最大堆为例,最小堆是一样的思想:
*将数组从第一个有叶节点的根节点开始维护,保证堆中的每个子树都是一个最大堆。
*将得到的最大的元素,也就是堆顶的元素,与堆的最后一个元素交换,然后维护根节点为堆顶节点的堆为最大堆。
这里有一个特殊的处理,即堆顶的节点的下标是1。为什么呢?因为这样根节点的左右子树分别是2*i和2*i+1,便于计算。

//将开始节点s到nLen组成的堆调整为最大堆void MaxHeapAdjust(int *arr, int s, int nLen){if(!arr || s < 1 || nLen < 1 || s > nLen)return;int i;int t = arr[s];for(i = s<<1; i <= nLen; i = s<<1){if(i < nLen && arr[i] < arr[i|1])//找到左右子树中最大者i |= 1;if(t > arr[i])//如果最大者小于t,则沉到底了break;arr[s] = arr[i];//较大者放在根的位置s = i;//更新初始位置}arr[s] = t;}//对数组arr进行堆排序void HeapSort(int *arr, int nLen){if(!arr || nLen < 1)return;int i;int t;for(i = nLen>>1; i > 0; --i)MaxHeapAdjust(arr, i, nLen);for(i = nLen; i > 1; --i)//最后只有一个元素,不用排{t = arr[i];//把最大值换到最后arr[i] = arr[1];arr[1] = t;MaxHeapAdjust(arr, 1, i - 1);}}

PS:附上最小堆排序的代码,在编程之美系列之寻找最大k个数里面会用到:

//将开始元素s到nLen组成的堆调整为最小堆void MinHeapAdjust(int *arr, int s, int nLen){if(!arr || s < 1 || nLen < 1 || s > nLen)return;int i,t;t = arr[s];for(i = s<<1; i <= nLen; i = s<<1){if(i < nLen && arr[i] > arr[i|1])//找到左右子树中最小的一个i |= 1;if(t < arr[i])//t已经很小了,就不往下沉了break;arr[s] = arr[i];s = i;}arr[s] = t;}//最小堆排序void MinHeapSort(int *arr, int nLen){if(!arr || nLen < 1)return;int i;int t;for(i = nLen>>1; i > 0; --i)MinHeapAdjust(arr, i, nLen);for(i = nLen; i > 1; --i){t = arr[1];arr[1] = arr[i];arr[i] = t;//调整剩下的i-1个数组成的堆MinHeapAdjust(arr, 1, i - 1);}}