【数据结构】快速排序

来源:互联网 发布:用户购买数据分析 编辑:程序博客网 时间:2024/06/01 18:48

一、快排的几种实现方法

1.左右指针法

a>算法思想:选定数组的最后一个数作为关键值(key),从左边开始遍历找比key大的,从右边找比key小的,找到后交换左右指针,让比key大的数据向后调,比key小的数据调到前面,等到左右指针相等的时候,把数组的最后一位和指针所指位置交换,这样一趟排序过后,key值的前面都是比它小的数,后面都比它大。此时key相当于划分了它左右两个区间,那么两个区间再次分别像刚才一样进行选key交换,会不断的划分新区间,直至区间只包含一个数,就有序了。


单趟排序的图示

    我们知道,每次单趟排序之后将数组分为了两个区间,而每个区间也都需要分别排序,故我们可以考虑用递归去实现。

    b>优化:假设每一次选的key值都是数组里最大或者最小的数,那快排就会变得很慢很慢,可以认为变成了冒泡排序,所以,key的选取比较关键,我们希望每次选到的是值比较居中的数,所以,key的选取使用三数取中法,即比较区间的第一个数,中间的数和最后一个数,选出值位于中间的数与最后一位交换,因为我们每次取的key是区间的最后一位,这样就尽可能的保证key的值贴近于中间值。

    c>时间复杂度:快排每次都会划分区间,直至区间中只有一个数,这一过程跟二叉树结构比较类似,单趟排序交换数的过程近似相当于遍历了这n个数,外层需要高度次递归,故,时间复杂度为O(n*log n).

    d>空间复杂度:递归了高度次,即空间复杂度为O(log n)

    e>代码实现

//左右指针法int PartSort(int* a, int begin, int end)  //单趟排序{int left = begin;int right = end;int mid = GetBestKey(a, begin, end);   //三数取中法得到keyswap(a[mid], a[end]);int key =a[end];while (left < right){while (left < right && a[left] <= key){left++;}while (left < right && a[right] >= key){right--;}if (a[left]>a[right])    swap(a[left], a[right]);}swap(a[left], a[end]);return left;}
void QuickSortR(int* a,int begin,int end)   //递归版{int mid;if (begin < end){mid = PartSort(a, begin, end);QuickSortR(a, begin, mid - 1);QuickSortR(a, mid + 1, end);}}

//key取值的优化int GetBestKey(int* a,int begin,int end)   {if ((end - begin) < 1)return end;int mid = begin + (end - begin) / 2;if (a[begin] < a[end]){if (a[mid] < a[begin])return begin;else if (a[mid]>a[end])return end;elsereturn mid;}else{if (a[end] > a[mid])return end;else if (a[mid] > a[begin])return begin;elsereturn mid;}}

2.挖坑法

    a>算法思想:挖坑法其实和前后指针法也比较相似,区间的最后一个数保存在key里之后,最后一个位置就视为一个坑,先从左边开始找比key大的,找到了放进刚才的坑里,左边又产生了一个新坑,右边找比key小的,找到了填到左边的坑。最后左右相遇于坑处,key放到此处,单趟排序就结束了,之后再递归,和第一种方法一样。所以时间复杂度,空间复杂度和第一种方法一致。

    b>优化:对排序整体进行优化,我们知道,当大量数据进行排序时,递归是划算的,可是如果当区间已经被划分的很小的话,不用去递归,改用直接插入排序也很快,因为区间很小,由快排的特性知,此时的小区间接近有序,而接近有序的时候插入排序是O(n)。还节省了快排递归时压栈的开销。(所有的快排方法都可以进行这样的优化)

    c>代码实现

//挖坑法进行单趟排序int PartSort1(int*a, int begin, int end){int mid = GetBestKey(a, begin, end);swap(a[mid], a[end]);int key = a[end];int left = begin;int right = end;while (left < right){while (left < right && a[left] <= key){left++;}a[right] = a[left];while (left<right && a[right] >= key){right--;}a[left] = a[right];}a[left] = key;return left;}
void QuickSortR(int* a,int begin,int end)   //递归版{int mid;if (begin < end){mid = PartSort1(a, begin, end);if (end - begin < 4)            //区间数值个数小于4的时候直接插入排序{InSertSort(a + begin, end - begin + 1);}mid = PartSort2(a, begin, end);QuickSortR(a, begin, mid - 1);QuickSortR(a, mid + 1, end);}}

3.前后指针法

    a>算法思想:定义两个指针prev和cur,cur指向区间第一个数,prev指向它的前一个。从左向右找比key小的,若当前数比key大,则cur向前移动,直到找到一个比key小的,此时prev向前移动一位,若prev和cur不指向同一位置,则prev所指位置的数值一定比cur大,所以交换prev和cur所指向的数。等到cur指向了最右边的数,再把最后一个数和prev的下一个位置的数交换,一趟排序就结束了。


前后指针法图示

b>代码实现

//前后指针法int PartSort2(int*a, int begin, int end){int prev = begin - 1;int cur = begin;int mid = GetBestKey(a, begin, end);swap(a[mid], a[end]);int key = a[end];while (cur < end){while (a[cur] < key && ++prev != cur){swap(a[cur], a[prev]);}++cur;}swap(a[end], a[++prev]);return prev;}
c>单趟排序后还要继续往下排,之前我们用的都是递归的方法,此处我们把它转化为非递归,要用到栈。代码如下

void QuickSortNoR(int *a, int begin, int end)//非递归{assert(a);stack<int> s;if (begin < end){s.push(end);s.push(begin);while (!s.empty()){int left = s.top();s.pop();int right = s.top();s.pop();int mid = PartSort2(a, left, right);if (mid - 1 > left){s.push(mid - 1);s.push(left);}if (right > mid + 1){s.push(right);s.push(mid + 1);}}}}

    快排的三种方式都理解了之后,个人觉得前后指针法最简单,代码实现起来很容易,同时要注意,如果是大量数据排序的话,最好进行一下方法二里面提到的优化,一是能提高效率,二也能防止不断的递归导致栈溢出。


2 1
原创粉丝点击