1.冒泡排序
待补充
2.交换排序
同冒泡排序一样,快速排序也属于交换排序,通过元素之间的比较和交换位置来达到排序的目的。
冒泡排序一轮只把一个元素移动到数列的一端;而快速排序在每一轮挑选一个基准元素,并让其它比它小的元素在数列一边,比它大的元素在数列的另一边,从而将数列分解成了两部分。
交换排序是分治法的一种求解思路,每一轮将元素分成两段,直到不可分为止,平均情况下需要logN轮,因此快排的平均时间复杂度是O(NlogN)
依据这个思想
void quickSort(int arr[], int startIndex, int endIndex){
//递归结束条件
if(startIndex >= endIndex){
return;
}
//计算基准元素位置
int pivotIndex = partition(arr,startIndex,endIndex);
//用分治法递归数列两部分
quickSort(arr,startIndex,pivotIndex - 1);
quickSort(arr,pivotIndex + 1, endIndex);
}
现在问题转换成用什么方法找出pivotIndex?
方法一:挖坑法
给定原数列,要求从小到达排序:
第一步:选择基准元素pivot,记住基准元素index,设置left和right分别位于数列的两个端点。
第二步(循环体):从right开始向左遍历数列,一旦遇到比基准数字小的数字就将这个数字“填入”“坑”(第一次“坑”是基准元素所在的位置)。同时left向右移动一位。
当前数列中,1<4,把1填入“坑”的位置。此时,元素1的位置变成了新的“坑”。同时,left向右移动一位。
接下来,切换到left指针进行比较。从left开始向右遍历数列,一旦遇到比基数字大的数字就将这个数字填入“坑”。同时,right向左移动一位。
此时,right右边橙色的区域代表着大于基准元素的区域,left左边黄色区域代表着小于基准元素的区域。
跳出循环的条件:left等于right的时候,此时数列左边的元素都小于4,数列右边的元素都大于4,这一轮交换终告结束。
代码实现:
int partition(int arr[], int startIndex, int endIndex){
//取第一个位置的元素作为基准元素
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
//"坑"的位置,初始等于pivot的位置
int index = startIndex;
//大循环在左右指针重合或者交错时候结束
while (left <= right){
//right指针从右往左进行比较
while(left <= right ){
if(arr[right] < pivot){
arr[left] = arr[right];
index = right;//更新“坑”的位置
left++;
break;
}
right--;
}
//left指针从左往右比较
while (left <= right){
if(arr[left] > pivot){
arr[right] = arr[left];
index = left;
right--;
break;
}
left++;
}
}
arr[index] = pivot;
return index;
}
方法二:交换法
仍然沿用第一个例子,给定数列,从小到大排序:
第一步:同挖坑法基本一样。选定基准元素pivot,设置left和right分别位于数列的两个端点。这里不用“坑”,也就不用记住基准元素的位置。
第二步(循环体):从right指针开始向左遍历,一旦遇到小于pivot的元素,则停止遍历,切换到left指针。【当前数列中,1<4,right直接停止遍历,切换到left指针。】
轮换到left指针移动,向右遍历,一旦遇到大于pivot的元素,则停止遍历。【left一开始指向的是基准元素,判断肯定满足条件,left向右一位,7>4,left指针在这里停下。】
令left和right指向的元素进行交换。
循环结束的条件?
第三步:退出循环后,令pivot元素和【left与right重合点元素】交换。此时,数列左边都小于4,右边都大于4,这一轮结束。
代码实现:
int partition(int arr[], int startIndex, int endIndex){
//取第一个元素作为基准元素
int pivot = arr[startIndex];
int left = startIndex;
int right = endIndex;
while (left != right){
//控制right指针比较并左移动
while (left < right && arr[right] > pivot){
right--;
}
//控制left指针比较并右移动
while (left < right && arr[left] <= pivot){
left++;
}
if(left < right){
int p = arr[left];
arr[left] = arr[right];
arr[right] = p;
}
}
int p = arr[left];
arr[left] = arr[startIndex];
arr[startIndex] = p;
return left;
}
和挖“坑”法相比,交换法在partition函数中进行的元素交换次数更少。
基准元素选择第一个出现的问题:一个逆序序列,期望你顺序排序。这种情况下无法体现分治法的优势,时间复杂度退化成O(N2).
能否用非递归实现快速排序呢?
答案是可以的.绝大多数用递归来实现的问题,都可以用栈的方式来代替。
因为代码中一层一层的方法调用,本身就是一个函数栈。每次进入一个新函数,就相当于入栈;每次有函数返回,就相当于出栈。
非递归实现
可以把原本的递归实现转化成一个栈的实现,在栈当中存储每一次函数调用的参数:
代码实现:
void quickSort(int arr[], int startIndex, int endIndex){
stack<int> stk;
if(startIndex < endIndex){
int pivot = partition(arr,startIndex,endIndex);
if(pivot - 1 > startIndex){
stk.push(startIndex);
stk.push(pivot - 1);
}
if(pivot + 1 < endIndex){
stk.push(pivot + 1);
stk.push(endIndex);
}
while (!stk.empty()){
int right = stk.top();//保存的某区间的右端点
stk.pop();
int left = stk.top();//保存的某区间的左端点
stk.pop();
pivot = partition(arr,left,right);
if(pivot - 1 > left){
stk.push(left);
stk.push(pivot - 1);
}
if(pivot + 1 < right){
stk.push(pivot + 1);
stk.push(right);
}
}
}
}
函数引入了一个存储int类型元素的栈,用于存储每一次交换时的起始下标和结束下标。
每一次循环,都会让栈顶元素出栈,进行排序,并且按照基准元素的位置分成左右两部分,左右两部分再分别入栈。当栈为空时,说明排序已经完毕,退出循环。