快速排序
思想:选择一个基准,下标h指向最后一个元素,下标l指向第一个元素。下标h从后向前遍历,比基准小的数字放在前面,下标l从前向后遍历,比基准大的放后面。重复以上操作,直到 l、h相遇,相遇点即为基准要放的位置。直到只剩一个数字的时候,停止划分。
时间复杂度:平均O(nlogn) 最好 O(nlogn) 最坏O(n^2)
空间复杂度:O(logn)
稳定性:不稳定
优点:越无序越快
缺点:有序的时候效率低
适用于:乱序的序列
不论用递归还是非递归,都需要这个一次划分过程
int Pariton(int *arr,int low,int high)//一次划分
{
if(arr == NULL || low < 0 || high < 0 || low > high) return-1;
优化2:int mid = (high - low)/2 + low;
FindSecondMaxNumber(arr,low,mid,high);
int i = low;
int j = high;
int tmp = arr[low];//选择基准点
while(i < j)//开始循环
{
while(i<j && tmp <= arr[j]) j--;//是否往前走
if(i < j) arr[i]=arr[j];
while(i < j && tmp >= arr[i]) i++;//是否往后走
if(i < j) arr[j] = arr[i];
}
arr[i] = tmp;//找到基准点要放的位置
return i;
}
用递归进行排序:
void QSort(int *arr,int low,int high)
{
int par = Pariton(arr,low,high);//先一次划分,par是一次遍历以后基准要放的位置。此时par左边的数字都小于par,par右边的数字都大于par。
if(par > low+1)//不断划分左边,par > low+1是因为可以保证par左边最少有两个数字,才再次进行划分
{
QSort(arr,low,par);
}
if(par < high - 1)//不断划分右边,前提是par右边至少有两个数字
{
QSort(arr,par+1,high);
}
}
void QuickSort(int *arr,int len)
{
QSort(arr,0,len-1);
}
非递归的方法,用栈实现:
void QuickSortstack(int*arr,int len)
{
int i = 0;;
int j = len-1;
stack<int> st;
int par = Pariton(arr,i,j);//先一次划分,par是一次遍历以后基准要放的位置
if(par > i+1)//左边继续划分
{
st.push(i);//入栈时先如左下标
st.push(par);//再入这次划分中的右下标
}
if(par < j - 1)
{
st.push(par+1);//入栈时先入这次划分的左下标
st.push(j);//再入右下标
}
while(!st.empty())
{
j = st.top(); st.pop();//出栈时先出右下标
i = st.top(); st.pop();//再出左下标
int par = Pariton(arr,i,j);//在while循环里面继续划分
if(par > i+1)
{
st.push(i);
st.push(par);
}
if(par < j - 1)
{
st.push(par+1);
st.push(j);
}
}
}
优化1:三分取中法
引入三分取中的原因是:选取基准时,用第一个元素作为基准这是很不好的方法,最坏情况(有序)时间复杂度还是O(n^2)。所以最佳的划分思路是将待排序序列分成等长的子序列,但是如果一定要刚好等长的话这会很难计算。
因此,一般取左端、右端和中间三个位置上元素中的中间值作为基准,假如下标用low、mid、high来表示,那么将这个中间值存储在low下标处,依然将low处作为基准。
先提供交换函数,目的是将数组中的左、中、右三个位置上的值进行按大小排列,大小顺序要求是 arr[mid] < arr[low] < arr[high]
,low处存储的是“中间值”
void Swap(int *arr,int firstindex,int secondindex)
{
int tmp = arr[firstindex];
arr[firstindex] = arr[secondindex];
arr[secondindex]=tmp;
}
接下来是把中间值放在合适的位置上,“中间值”其实也就是左、中、右三个断点的第二大的值
void FindSecondMaxNumber(int *arr,int low,int mid,int high)
{ //目标是arr[mid] < arr[low] < arr[high]
if(arr[mid] > arr[high])//目标是mid的值比high小
Swap(arr,mid,high);
if(arr[mid] > arr[low])//目标是mid的值比low小
Swap(arr,mid,low);
if(arr[low] > arr[high])//目标是low的值比high小
Swap(arr,low,high);
}
以上就是第一种“三分取中”的优化
优化2:当数据量较小时,比如10个元素,用直接插入排序即可。
if(high - low +1 < 10)
{
insertSort(arr,low,high);
return ;
}
优化3:聚集相同的关键字/基准点,不进行下一趟排序
如果在待排序序列中,有和基准点相同的值,那么我们可以将这些相同的值放在一起,每次递归时将相同基准值的最左和最右传进去,这样这些相同的数字就不用进行排序
void gather(int arr[],int low,int high,int par,int *left,int* right)
{
if(low < high)//首先要满足这个条件
{
int count = par - 1;//1.在基准左侧寻找与基准相同的值
for(int i = count ; i >=low;--i)
{
if(i != count && arr[i] == arr[par])
//i不是count并且当前值和基准点相同
{
Swap(arr,i,count);
count--;//向左减
}
}
*left = count;//记录相同关键字的左下标
count = par + 1;//2.在基准右侧寻找与基准相同的值
for(int i= count ; i<=high;++i)
{
if(i != count && arr[i] == arr[par])
{
Swap(arr,i,count);
count++;//向右加
}
}
*right = count;//记录相同关键字的右下标
}
}
//下面这个函数仅仅是如何在递归中调用聚集关键字的函数
void Qsort(int *arr,int low,int high)
{
int par = Pariton(arr,low,high);
int left = low;//left将要记录相同基准的左下标
int right = high;//right将要记录相同基准的右下标
gather(arr,low,high,par,left,right);//优化3
if(par > low+1)
{
QSort(arr,low,left);//传入右下标时传入记录的相同基准的左边就可以,相同基准不用再进行排序
}
if(par < high - 1)
{
QSort(arr,right,high);//传入左下标时传入记录的相同基准的右边就可以,相同基准不用再进行排序
}
}