一、快速排序
1、取序列里的任意位置作为基准值;
2、通过一种方法,将序列分为左右两部分,左边的元素比基准值小,右边的元素比基准值大。基准值位于中间;
3、此时左右部分均无序,在左边部分取基准值,并且把左部分划分为两部分,右边同理,循环执行
2、通过一种方法,将序列分为左右两部分,左边的元素比基准值小,右边的元素比基准值大。基准值位于中间;
3、此时左右部分均无序,在左边部分取基准值,并且把左部分划分为两部分,右边同理,循环执行
//分割的方式 int Partion(int *array, int left, int right) { //取基准值 int key = array[right - 1]; int begin = left, end = right-1; while (begin < end)//区间内还有元素 { //从前往后找,找比基准值大的元素 while (begin<end&&array[begin] <= key) ++begin; //从后往前找,找比基准值小的元素 while (begin<end&&array[end] >= key) --end; if (begin<end) //交换begin和end的位置 Swap(&array[begin], &array[end]); } if (begin != right - 1)//begin和end不是同一个位置 Swap(&array[begin], &array[right - 1]); return begin; } void QuickSort(int *array, int left, int right) { //只剩一个元素不需要排序 int div = 0; if (right - left > 1) { //分割,返回基准值的下标 div=Partion(array, left, right); //对左半边进行排序 QuickSort(array, left, div); //对右半边进行排序 QuickSort(array, div+1, right); } }
分割基准值的方法
法一:挖坑法:
1、取序列里的任意位置作为基准值(如取最右边的的元素);将序列分为左右两部分,左边的元素比基准值小,右边的元素比基准值大。
2、将基准值的值移走,坑还在。从前往后找,找比基准值大的元素,用这个元素的值填充基准值的坑,在此处形成一个坑。从后往前找,找比基准值小的元素
,用此处的元素填充上一个(左边的坑begin),此处形成一个坑……begin向后走(或者end往前走)循环进行……用基准值填充最后剩下的那一个坑。
2、将基准值的值移走,坑还在。从前往后找,找比基准值大的元素,用这个元素的值填充基准值的坑,在此处形成一个坑。从后往前找,找比基准值小的元素
,用此处的元素填充上一个(左边的坑begin),此处形成一个坑……begin向后走(或者end往前走)循环进行……用基准值填充最后剩下的那一个坑。
//挖坑法 int Partion1(int *array, int left, int right) { //取基准值 int key = array[right - 1]; int begin = left, end = right - 1; while (begin < end)//区间内还有元素 { //从前往后找,找比基准值大的元素 while (begin<end&&array[begin] <= key) ++begin; if (begin < end) { array[end] = array[begin]; --end; } //从后往前找,找比基准值小的元素 while (begin<end&&array[end] >= key) --end; if (begin < end) { array[begin] = array[end];//上一个坑在begin的位置上 ++begin; } } array[begin] = key; return begin; } void QuickSort(int *array, int left, int right) { //只剩一个元素不需要排序 int div = 0; if (right - left > 1) { //分割,返回基准值的下标 div=Partion1(array, left, right); //对左半边进行排序 QuickSort(array, left, div); //对右半边进行排序 QuickSort(array, div+1, right); } }
法二:前后指针:
1、取基准值,
2、从cur的位置开始找,找比基准值小的元素,找到后pre向前走一步,若是pre向前走一步后与cur相遇,cur向后走一步,不进行其他操作;若是找到比基准值大的元素,cur向后走一步,pre不向后走,而且不进行其他操作。
,若是pre向前走一步后与cur不相遇,交换cur和pre的值(数据,不交换位置),cur朝后走一步。重复进行越界后,交换cur与pre++的值,因为pre++后的值比基准值大。
1、取基准值,
2、从cur的位置开始找,找比基准值小的元素,找到后pre向前走一步,若是pre向前走一步后与cur相遇,cur向后走一步,不进行其他操作;若是找到比基准值大的元素,cur向后走一步,pre不向后走,而且不进行其他操作。
,若是pre向前走一步后与cur不相遇,交换cur和pre的值(数据,不交换位置),cur朝后走一步。重复进行越界后,交换cur与pre++的值,因为pre++后的值比基准值大。
int Partion2(int *array, int left, int right) { int cur = left, pre = cur-1; int key = right - 1;//取基准值为最右边的元素 while (cur < right) { if (array[cur] < key&&++pre != cur) Swap(&array[cur], &array[pre]); cur++; } if (++pre != right) Swap(&array[pre], &array[right - 1]); return pre; } void QuickSort(int *array, int left, int right) { //只剩一个元素不需要排序 int div = 0; if (right - left > 1) { //分割,返回基准值的下标 div=Partion2(array, left, right); //对左半边进行排序 QuickSort(array, left, div); //对右半边进行排序 QuickSort(array, div+1, right); } }
快速排序的时间复杂度:单次划分的时间复杂度为O(n),最差情况下(给定的序列接近有序)O(n^2),效率很差;最优情况下,每次取一个元素都可以将序列分为相等的两部分:比较n次,所以有n层。第一层,O(n),第二层2,2*O(n/2),第三层,3*O(n/3)……
第n层,n*O(n/n)==o(n)最后计算得:O(n*lgN):每层比较次数为n,划分为lgN层(深度)。
快速排序的空间复杂度:递归的深度(树的高度n):O(lgN)
快速排序的稳定性:不稳定,跨区间交换元素
避免选中的基准值正好为最大或者最小值,不适用于近似有序的序列。
第n层,n*O(n/n)==o(n)最后计算得:O(n*lgN):每层比较次数为n,划分为lgN层(深度)。
快速排序的空间复杂度:递归的深度(树的高度n):O(lgN)
快速排序的稳定性:不稳定,跨区间交换元素
避免选中的基准值正好为最大或者最小值,不适用于近似有序的序列。
快排适用于很随机的数据的排序。
二、快速排序的优化:
1、基准值的选取:
(1)缺点:选中的基准值可能正好为最大或者最小值,应避免取到最大值或者最小值。若是基准值为最小值,数据全部都会位于基准值的右边,而基准值为最大值,数据全部位于基准值的左边。但是我们选取基准值是为了将数据分为左右两部分。
若元素是有序的,排序后类似于单只树,使效率低下
优化:不取最大值或最小值作为基准值,不从最右侧开始取基准值,一次取三个值,取这三个值中间数据作为基准值。降低取到最大值或最小值作为基准值的概率。
二、快速排序的优化:
1、基准值的选取:
(1)缺点:选中的基准值可能正好为最大或者最小值,应避免取到最大值或者最小值。若是基准值为最小值,数据全部都会位于基准值的右边,而基准值为最大值,数据全部位于基准值的左边。但是我们选取基准值是为了将数据分为左右两部分。
若元素是有序的,排序后类似于单只树,使效率低下
优化:不取最大值或最小值作为基准值,不从最右侧开始取基准值,一次取三个值,取这三个值中间数据作为基准值。降低取到最大值或最小值作为基准值的概率。
int GetMiddleIndex(int *array, int left, int right) { int mid = left + ((right - left) >> 1); if (array[left] < array[right])//左边小于右边 { if (array[mid] < array[left])//中间数据小于最小的元素 return left; else if (array[mid]>array[right])//中间数据大于最大的元素 return right; else//位于中间数据 return mid; } else { if (array[mid] > array[left])//中间数据大于最大的数据 return left; else if (array[mid]<array[right])//中间数据小于最小的数据 return right; else return mid; } } //分割的方式 int Partion(int *array, int left, int right) { //取基准值 int key = 0; int begin = left, end = right - 1; int midIndex = GetMiddleIndex(array, left, right - 1); //把中间值换到最右侧 Swap(&array[midIndex], &array[end]); key = array[end]; while (begin < end)//区间内还有元素 { //从前往后找,找比基准值大的元素 while (begin<end&&array[begin] <= key) ++begin; //从后往前找,找比基准值小的元素 while (begin<end&&array[end] >= key) --end; if (begin<end) //交换begin和end的位置 Swap(&array[begin], &array[end]); } if (begin != right - 1)//begin和end不是同一个位置 Swap(&array[begin], &array[right - 1]); return begin; } int Partion1(int *array, int left, int right) { //取基准值 //找中间值,即基准值的位置 int key = 0; int begin = left, end = right - 1; int midIndex = GetMiddleIndex(array, left, right - 1); //把中间值换到最右侧 Swap(&array[midIndex], &array[end]); key = array[end]; while (begin < end)//区间内还有元素 { //从前往后找,找比基准值大的元素 while (begin<end&&array[begin] <= key) ++begin; if (begin < end) { array[end] = array[begin]; --end; } //从后往前找,找比基准值小的元素 while (begin<end&&array[end] >= key) --end; if (begin < end) { array[begin] = array[end];//上一个坑在begin的位置上 ++begin; } } array[begin] = key; return begin; } int Partion2(int *array, int left, int right) { int cur = left, pre = cur - 1; int key = right - 1;//取基准值为最右边的元素 int end = right - 1; int midIndex = GetMiddleIndex(array, left, right - 1); //把中间值换到最右侧 Swap(&array[midIndex], &array[end]); key = array[end]; while (cur < right) { if (array[cur] < key&&++pre != cur) Swap(&array[cur], &array[pre]); cur++; } if (++pre != right) Swap(&array[pre], &array[right - 1]); return pre; } void QuickSort(int *array, int left, int right) { //只剩一个元素不需要排序 int div = 0; if (right - left > 1) { //分割,返回基准值的下标 div=Partion2(array, left, right); //对左半边进行排序 QuickSort(array, left, div); //对右半边进行排序 QuickSort(array, div+1, right); } }
(2)插入排序适用于数据量小的,而快速排序则不适用。我们可以先判断是不是数据量很小,再选择排序的方法
void QuickSort(int *array, int left, int right) { //只剩一个元素不需要排序 int div = 0; if (right - left<16) InsertSort(array, right - left); else { //分割,返回基准值的下标 div=Partion2(array, left, right); //对左半边进行排序 QuickSort(array, left, div); //对右半边进行排序 QuickSort(array, div+1, right); } }划分的函数 Partion2(array, left, right)同上
2、将递归的排序转换为循环的排序(用栈实现)
划分的函数
Partion2(array, left, right)
同上
void QuickSortNor(int *array, int size) { int div = 0; Stack s; StackInit(&s); //栈是先进后出的,先放右边界,则先取出左边界 StackPush(&s, size); StackPush(&s, 0); while (!Stackempty(&s)) { int left = 0; int right = 0; //先取左边界 left = StackTop(&s); //将左边界拿出来 StackPop(&s); //取右边界 right = StackTop(&s); //将右边界拿出来 StackPop(&s); if (left < right)//为有效边界 { //划分 div = Partion2(array, left, right); //先处理左半部分,则先将右半部分压栈 StackPush(&s, right);//右半部分的右边界 StackPush(&s, div + 1);//右半部分的左边界 StackPush(&s, div);//左半部分的右边界 StackPush(&s, left);//左半部分的左边界 } } }