快速排序的的基本思想:
设数组a中存放了n个数据元素,low为数组的低端下标,high为数组的高端下标,从数组a中任取一个 元素(通常选取a[ow])作为标准,调整数组a中各个元素的位置,使排在标准元素前面的元素的关键字均小于标准元素的关键字,排在标准元素后面元素的关键字均大于或等于标准元素的关键字。一次结束后,把标准元素放在适合的位置,并且把以标准元素为中心划分了两个子数组,前面数组的数都小于标准元素,后面的数组都大于标准元素。然后分别对这两个自数组进行相同的递归快速排序。递归算法的出口是high>low
快速排序相当与冒泡排序的升级版
1.选择基准:在待排序列中,按照某种方式挑出一个元素,作为 "基准"(pivot)
2.分割操作:以该基准在序列中的实际位置,把序列分成两个子序列。此时,在基准左边的元素都比该基准小,在基准右边的元素都比基准大
3.递归地对两个序列进行快速排序,直到序列为空或者只有一个元素。
挖坑填数,进行快速排序:
1.i =L; j = R; 将基准数挖出形成第一个坑a[i]。
2.j--由后向前找比它小的数,找到后挖出此数填前一个坑a[i]中。
3.i++由前向后找比它大的数,找到后也挖出此数填到前一个坑a[j]中。
4.再重复执行2,3二步,直到i==j,将基准数填入a[i]中
快速排序的过程图:
快速排序的完整代码(递归):
1
#include<iostream> using namespace std; int partition(int *arr,int left,int right) { int temp=arr[left]; while(left<right)//直达left和right重合的时候,才找到合适的位置 { //先从后往前找比基准小的 while(left<right && arr[right]>=temp)//当right的值大于temp的值的时候才执行 //等号一定得写,因为可能会出现,保存的temp元素和数据中的元素一样的,不写会出现死循环的现象 { right--; } arr[left]=arr[right];//当right的值小于temp的值的时候执行 //从前往后找,找比基准大的 while(left<right && arr[left] <=temp)//当left的值小于temp的值的时候执行 { left++; } arr[right]=arr[left];//当left的值大于temp的时候执行 } arr[left]=temp;//此时的left和right在同一个位置,此时为合适的位置,把temp的值给left return left;//此时返回的值是temp合适的位置,即小于它的在它的左边,大于它的在它的右边 } void quick(int *arr,int left,int right) { if(left<right) { int pivot=partition(arr,left,right); quick(arr,left,pivot-1); quick(arr,pivot+1,right); } } void quick_sort(int *arr,int len) { quick(arr,0,len-1); } int main() { int arr[]={9,5,7,10,45,12}; int len=sizeof(arr)/sizeof(arr[0]); quick_sort(arr,len); for(int k=0;k<len;++k) { cout<<arr[k]<<" "; } cout<<endl; }
快速排序的完整代码(非递归)
1 #include<stack> 2 #include<iostream> 3 using namespace std; 4 5 int partition(int *arr,int left,int right) 6 { 7 int temp=arr[left];//基准 8 while(left<right) 9 { 10 //先从后往前找比基准小的 11 while(left<right && temp<=arr[right]) 12 { 13 right--; 14 } 15 arr[left]=arr[right]; 16 //从前往后找比基准大的 17 while(left<right && temp>=arr[left]) 18 { 19 left++; 20 } 21 arr[right]=arr[left]; 22 } 23 arr[left]=temp; 24 return left; 25 } 26 //其实就是用栈保存每一个待排序子串的首尾元素的下标 27 void q_sort(int *arr,int left,int right ) 28 { 29 stack<int> st; 30 int pos=0; 31 st.push (left); 32 st.push (right); 33 while(!st.empty()) 34 { 35 right=st.top(); 36 st.pop(); 37 left=st.top(); 38 st.pop ();//5,6,8,2,9,4,1,3,45,89,65 39 pos=partition(arr,left,right); 40 if(pos+1<right)//先入基准右边的,如果基准右边只有一个元素的时候,就不用入了 41 { 42 st.push (pos+1); 43 st.push (right); 44 } 45 if(pos-1>left)//再入基准左边的,如果基准左边只有一个元素的时候,就不用入了 46 { 47 st.push (left); 48 st.push (pos-1); 49 50 } 51 } 52 } 53 54 void quick_sort(int *arr,int len) 55 { 56 q_sort(arr,0,len-1); 57 } 58 int main() 59 { 60 int arr[]={5,6,8,2,9,4,1,3,45,89,65}; 61 int len=sizeof(arr)/sizeof(arr[0]); 62 quick_sort(arr,len); 63 for(int i=0;i<len;i++) 64 { 65 cout<<arr[i]<<" "; 66 } 67 cout<<endl; 68 }
快速排序的各种优化:
优化一:三数取中法,解决数据基本有序的(就是找到数组中最小下标,最大下标,中间下标的数字,进行比较,把中间大的数组放在最左边)
代码:
//*************************************************************************
void swap(int *arr,int left,int right)
{
int temp;
temp=arr[left];
arr[left]=arr[right];
arr[right]=temp;
}
//***************************************************************************
int partition(int *arr,int left,int right)
{
//***************************
//e.g:9,1,5,8,3,7,4,6,2
int m=left+(right-left)/2;//找到中间的数字的下标
if(arr[left]>arr[right])//最左大于最右的时候,交换左右
{
swap(arr,left,right);
}
if(arr[m]>arr[right])//如果中间的>right ,交换
{
swap(arr,m,right);
}
if(arr[m]>arr[left])//如果中间的>left,交换
{
swap(arr,m,right);
}
//经过交换之后low变为3
//****************************
int temp=arr[left];//基准
while(left<right)//知道left和right重合的时候,才找到合适的位置
{ //从后向前找到比小的数字
while(left<right && arr[right]>=temp)//当right的值大于temp的值的时候才执行
{
right--;
}
arr[left]=arr[right];//当right的值小于temp的值的时候执行
while(left<right && arr[left] <= temp)//从前往后找到比基准大的数字
{
left++;
}
arr[right]=arr[left];//当left的值大于temp的时候执行
}
arr[left]=temp;//此时的left和right在同一个位置,此时为合适的位置,把temp的值给left
return left;//此时返回的值是temp合适的位置,即小于它的在它的左边,大于它的在它的右边
}
优化二:
随机选取基准
引入的原因:在待排序列是部分有序时,固定选取枢轴使快排效率底下,要缓解这种情况,就引入了随机选取枢轴
思想:取待排序列中任意一个元素作为基准
/*随机选择枢轴的位置,区间在low和high之间*/
int SelectPivotRandom(int arr[],int low,int high)
{
//产生枢轴的位置
srand((unsigned)time(NULL));
int pivotPos = rand()%(high - low) + low;
//把枢轴位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数
swap(arr[pivotPos],arr[low]);
return arr[low];
}
优化三:优化小数组的交换,就是为了解决大才小用问题
原因:对于很小和部分有序的数组,快排不如插排好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插排而不是快排
截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。
#define max_len 10
void quick(int *arr,int left,int right)
{
int length=right-left;
if(length>max_len )
{
int pivot=partition(arr,left,right);
quick(arr,left,pivot-1);
quick(arr,pivot+1,right);
}
else
{
//用插入排序
}
}
优化四:
在一次分割结束后,可以把与Key相等的元素聚在一起,继续下次分割时,不用再对与key相等元素分割
举例:
待排序序列 1 4 6 7 6 6 7 6 8 6
三数取中选取枢轴:下标为4的数6
转换后,待分割序列:6 4 6 7 1 6 7 6 8 6
枢轴key:6
本次划分后,未对与key元素相等处理的结果:1 4 6 6 7 6 7 6 8 6
下次的两个子序列为:1 4 6 和 7 6 7 6 8 6
本次划分后,对与key元素相等处理的结果:1 4 6 6 6 6 6 7 8 7
下次的两个子序列为:1 4 和 7 8 7
经过对比,我们可以看出,在一次划分后,把与key相等的元素聚在一起,能减少迭代次数,效率会提高不少
具体过程:在处理过程中,会有两个步骤
第一步,在划分过程中,把与key相等元素放入数组的两端
第二步,划分结束后,把与key相等的元素移到枢轴周围
举例:
待排序序列 1 4 6 7 6 6 7 6 8 6
三数取中选取枢轴:下标为4的数6
转换后,待分割序列:6 4 6 7 1 6 7 6 8 6
枢轴key:6
第一步,在划分过程中,把与key相等元素放入数组的两端
结果为:6 4 1 6(枢轴) 7 8 7 6 6 6
此时,与6相等的元素全放入在两端了
第二步,划分结束后,把与key相等的元素移到枢轴周围
结果为:1 4 66(枢轴) 6 6 6 7 8 7
此时,与6相等的元素全移到枢轴周围了
之后,在1 4 和 7 8 7两个子序列进行快排
代码:
void QSort(int arr[],int low,int high)
{
int first = low;
int last = high;
int left = low;
int right = high;
int leftLen = 0; //用来统计左边与key相等的元素的个数
int rightLen = 0; //统计右边与key相等的元素的个数
if (high - low + 1 < 10)
{
InsertSort(arr,low,high); //数据量少,就用插入排序
return;
}
//一次分割
int key = SelectPivotMedianOfThree(arr,low,high);//使用三数取中法选择枢轴
while(low < high)
{
while(high > low && arr[high] >= key)
{
if (arr[high] == key)//处理相等元素
{
swap(arr[right],arr[high]); //把右边与key元素相等的聚集的右端
right--;
rightLen++;
}
high--;
}
arr[low] = arr[high];
while(high > low && arr[low] <= key)
{
if (arr[low] == key) //把左边与key元素相等的聚集数组的左端
{
swap(arr[left],arr[low]);
left++;
leftLen++;
}
low++;
}
arr[high] = arr[low];
}
arr[low] = key;
//一次快排结束
//把与枢轴key相同的元素移到枢轴最终位置周围
int i = low - 1; //轴的左边
int j = first;
while(j < left && arr[i] != key)
{
swap(arr[i],arr[j]); //此时,把数组左端与key相等的数据换到key的左边
i--;
j++;
}
i = low + 1; //轴的右边
j = last;
while(j > right && arr[i] != key)
{
swap(arr[i],arr[j]); //此时,把数组右端与key相等的数据换到,key右边
i++;
j--;
}
partition(arr,first,low - 1 - leftLen);
partition(arr,low + 1 + rightLen,last);
}
————————————————
版权声明:本文为CSDN博主「玲max」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lingling_nice/article/details/80943231