目录
快速排序(Quick Sort)
1、算法描述
2、算法分析——快速排序一次划分的三种实现方式
(1) 挖坑法
(2) 左右指针法
(3) 前后指针法
3、算法分析——快速排序基准数的三种选取方式
(1)固定位置选取基准
(2)随机位置选取基准
(3)三分取中法选取基准
4、算法分析——快速排序的两种优化
(1)当数据量很小时,采用直接插入排序
(2)聚集相同基准法
5、算法分析——快速排序的两种调用方式
(1)递归调用
(2)非递归调用
6、算法示例
(1)挖坑法
(2)随机基准法
(3)三分取中法
(4)插排 + 三分取中法
(5)聚集相同基准法 + 三分取中法
快速排序(Quick Sort)是对冒泡排序的一种改进。
快速排序由C. A. R. Hoare在1962年提出。并且在内存使用、程序实现复杂性上表现优秀,尤其是对快速排序算法进行随机化的可能,使得快速排序在一般情况下是最实用的排序方法之一。它的基本思想是:通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以通过递归进行,以此达到整个数据变成有序序列,快速排序也可通过非递归的方式实现。
快速排序被认为是当前最优秀的内部排序方法。
快速排序使用分治法来把一个串(list)分为两个子串(sub-lists)。具体算法描述如下:
基本思想:
注意:当以第一个数组元素为基准数时,搜索必须先从后向前进行,当以最后一个数组元素为基准数时,搜索必须先从前向后进行。
挖坑法图示:
(a)一趟排序的过程:
(b)排序的全过程
/* 挖坑法的基本思想就是将基准值保存起来(挖出)
** 从右到左查找比基准值小的值,然后与arr[low]交换(即high位置被挖出)
** 从左到右查找比基准值大的值,然后与arr[high]交换(即low位置被挖出)
** 然后将temp保存的基准值填入low的位置(或者high位置,因为low和high此时相等)
*/
int Partition(int *arr,int low,int high)
{
int temp = arr[low];
while(low < high)
{
while(low < high && arr[high] >= temp){high--;}
arr[low] = arr[high];
while(low < high && arr[low] <= temp){low++;}
arr[high] = arr[low];
}
arr[low] = temp;
return low;
}
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
/* 左右指针法进行一次划分
** 以0号元素为基准数,从右开始向左查找小于基准数的数字(high--),找到后停下来
** 再从左向右开始查找大于基准数的数字(low++),找到后停下来
** 如果low小于high,则交换他们的值
** 结束后将temp保存值与low的值(或high的值,因为此时low与high相等)交换
** 完成一次划分
*/
int Pritition(int *arr,int low,int high)
{
int temp = low;
while (low= arr[temp]){high--;}
while (low < high && arr[low] <= arr[temp]){low++;}
if(low < high){swap(arr,low,high);}
}
swap(arr,low,temp); // 或 swap(arr,high,temp);
return low; // 或 return high
}
定义两个指针,一前一后,前面指针找比基数小的数,后面指针找比基数大的数,前面的指针找到后,将前后指针所指向的数据交换,当前面的指针遍历完整个数组时,将基数值与后指针的后一个位置的数据进行交换,然后以后指针的后一个位置作为分界,然后将数组分开,进行递归排序。
int Partition(int arr[], int left, int right)
{
int fast = left; // 若arr[fast] >= key ,则fast++
int slow = fast-1 ; // arr[fast] >= key时,slow不动,直到fast找到大于小于key的数字,然后交换
int key = arr[right];
while (fast <= right)
{
if (arr[fast] <= key && ++slow != fast)
{
swap(arr,slow,fast);
}
fast++;
}
return slow;
}
基本思想:选取第一个或最后一个元素作为基准值。
注意:基本的快速排序选取第一个或最后一个元素作为基准。但是,这是一直很不好的处理方法。
int Partition(int *arr, int left, int right)
{
int i = left;
int j = right;
int k = arr[left]; // 使用首元素作为基准值
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
return i;
}
基本思想:选取待排序列中任意一个数作为基准值,选出的任意值与low下标元素交换,可继续使用Partition函数
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
int Partition(int *arr, int low, int high) // 快排一次划分算法
{
srand((unsigned)time(NULL));
swap(arr,low,low + rand() % (high-low+1));
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
return i;
}
基本思想:arr[mid] <= arr[low] <= arr[high],使得arr[low]处于三数中的中间值,可继续使用Partition函数
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
// arr[mid] <= arr[low] <= arr[high]
void SelectPivotMedianOfThree(int *arr,int low,int high)
{
int mid = (low+high) >> 1;
if(arr[mid] > arr[low])
{
swap(arr,mid,low);
}
if(arr[low] > arr[high])
{
swap(arr,low,high);
}
if(arr[mid] > arr[high])
{
swap(arr,mid,high);
}
}
int Partition(int *arr, int low, int high)// 快排一次划分算法
{
SelectPivotMedianOfThree(arr,low,high);
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
return i;
}
原因:对于很小和部分有序的数组,快速排序不如插入排序好。当待排序序列的长度分割到一定大小后,继续分割的效率比插入排序要差,此时可以使用插入排序
截止范围:待排序序列长度N = 10,虽然在5~20之间任一截止范围都有可能产生类似的结果,这种做法也避免了一些有害的退化情形。
void InsertSort(int *arr,int low,int high)
{
int tmp ;
for(int i = low+1;i <= high;i++)
{
tmp = arr[i];
int j = i-1;
while (j >= low && arr[j] > tmp)
{
arr[j+1] = arr[j];
j--;
}
arr[j+1] = tmp;
}
}
void Quick(int *arr,int low,int high)
{
int k = Partion(arr,low,high);
if(high-low+1<10) // 当整个序列的大小high-low+1<10时,采用直接插入排序
{
InsertSort(arr,low,high);
return;
}
else
{
if(low < k-1)
{
Quick(arr,low,k-1);
}
if(high > k+1)
{
Quick(arr,k+1,high);
}
}
}
void FocusNumPar(int *arr,int low,int par,int high,int *left,int *right)
{
int parLeft = par-1;
if(low < high)
{
for(int i=par-1;i>=low;i--)
{
if(arr[i]==arr[par])
{
if(i!=par)
{
swap(arr,i,parLeft);
parLeft--;
}
else
{
i--;
}
}
}
*left = parLeft;
int parRight = par+1;
for(int i=par+1;i<=high;i++)
{
if(arr[i]==arr[par])
{
if(i!=par)
{
swap(arr,i,parRight);
parRight++;
}
else
{
i++;
}
}
}
*right = parRight;
}
}
void Quick(int *arr,int low,int high)
{
ThreeMiddle(arr,low,high);
int left = 0;
int right = 0;
int k = Partion(arr,low,high);
FocusNumPar(arr,low,k,high,&left,&right);
if(low <= left-1)
{
Quick(arr,low,left);
}
if(high >= right+1)
{
Quick(arr,right,high);
}
}
快递排序大部分的版本都是递归调用的方式来实现的:通过Partition来实现划分,并递归实现前后的划分。
void Quick(int *arr,int low,int high)
{
int k = Partition(arr,low,high);
if(low < k-1)
{
Quick(arr,low,k-1);
}
if(high > k+1)
{
Quick(arr,k+1,high);
}
}
因为递归的本质是栈,因此我们非递归实现的过程中,借助栈来保存中间变量就可以实现非递归了。在这里中间变量也就是通过Pritation函数划分之后分成左右两部分的首尾指针,只需要保存这两部分的首尾指针即可。
void Quick(int *arr,int len) // 快排非递归调用
{
int Size = (int)log((double)len/(int)(double)2);
int *Stack = (int *)malloc(sizeof(int) * Size * len);
assert(Stack!=NULL);
int top = 0;
int low = 0;
int high = len-1;
int k = Partition(arr,low,high);
if(low < k-1)
{
Stack[top++] = low;
Stack[top++] = k-1;
}
if(high > k+1)
{
Stack[top++] = k+1;
Stack[top++] = high;
}
while (top > 0) //栈为空则排序完毕
{
high = Stack[--top];
low = Stack[--top];
k = Partition(arr,low,high) ;
if(low < k-1)
{
Stack[top++] = low;
Stack[top++] = k-1;
}
if(high > k+1)
{
Stack[top++] = k+1;
Stack[top++] = high;
}
}
free(Stack);
}
#include
void Show(int *arr,int len);
int Partition(int *arr,int low,int high)
{
static int n=1; // 记录排序次数
int temp = arr[low];
while(low < high)
{
while(low < high && arr[high] >= temp){high--;}
arr[low] = arr[high];
while(low < high && arr[low] <= temp){low++;}
arr[high] = arr[low];
}
arr[low] = temp;
printf("第%d步排序结果:",n++); // 输出每步排序结果
for(int i = 0;i < 10;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
return low;
}
void QuickSort(int *arr,int low,int high)
{
int k = Partition(arr,low,high);
if(low < k-1)
{
QuickSort(arr,low,k-1);
}
if(high > k+1)
{
QuickSort(arr,k+1,high);
}
}
void Show(int *arr,int len)
{
for(int i = 0;i < len;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {12,34,45,21,4,6,33,53,15,5};
int len = sizeof(arr)/sizeof(arr[0]);
printf(" 排序前:");
Show(arr,len);
QuickSort(arr,0,len-1);
printf(" 排序后:");
Show(arr,len);
}
运行结果:
#include
#include
#include
void Show(int *arr,int len);
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
int Partition(int *arr, int low, int high) // 快排一次划分算法
{
srand((unsigned)time(NULL));
swap(arr,low,low + rand() % (high-low+1));
printf("随机基准:%2d,",arr[low]);
static int n=1; // 记录排序次数
int temp = arr[low];
while(low < high)
{
while(low < high && arr[high] >= temp){high--;}
arr[low] = arr[high];
while(low < high && arr[low] <= temp){low++;}
arr[high] = arr[low];
}
arr[low] = temp;
printf("第%d步排序结果:",n++); // 输出每步排序结果
for(int i = 0;i < 10;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
return low;
}
void QuickSort(int *arr,int low,int high)
{
int k = Partition(arr,low,high);
if(low < k-1)
{
QuickSort(arr,low,k-1);
}
if(high > k+1)
{
QuickSort(arr,k+1,high);
}
}
void Show(int *arr,int len)
{
for(int i = 0;i < len;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {12,34,45,21,4,6,33,53,15,5};
int len = sizeof(arr)/sizeof(arr[0]);
printf(" 排序前:");
Show(arr,len);
QuickSort(arr,0,len-1);
printf(" 排序后:");
Show(arr,len);
}
运行结果:
#include
void Show(int *arr,int len);
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
// arr[mid] <= arr[low] <= arr[high]
void SelectPivotMedianOfThree(int *arr,int low,int high)
{
int mid = (low+high) >> 1;
if(arr[mid] > arr[low])
{
swap(arr,mid,low);
}
if(arr[low] > arr[high])
{
swap(arr,low,high);
}
if(arr[mid] > arr[high])
{
swap(arr,mid,high);
}
}
int Partition(int *arr, int low, int high)// 快排一次划分算法
{
SelectPivotMedianOfThree(arr,low,high);
printf("基准数为%2d,",arr[low]);
static int n=1; // 记录排序次数
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
printf("第%d步排序结果:",n++); // 输出每步排序结果
for(int i = 0;i < 10;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
return i;
}
void QuickSort(int *arr,int low,int high)
{
int k = Partition(arr,low,high);
if(low < k-1)
{
QuickSort(arr,low,k-1);
}
if(high > k+1)
{
QuickSort(arr,k+1,high);
}
}
void Show(int *arr,int len)
{
for(int i = 0;i < len;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {12,34,45,21,4,6,33,53,15,5};
int len = sizeof(arr)/sizeof(arr[0]);
printf(" 排序前:");
Show(arr,len);
QuickSort(arr,0,len-1);
printf(" 排序后:");
Show(arr,len);
}
运行结果:
#include
void Show(int *arr,int len);
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
// arr[mid] <= arr[low] <= arr[high]
void SelectPivotMedianOfThree(int *arr,int low,int high)
{
int mid = (low+high) >> 1;
if(arr[mid] > arr[low])
{
swap(arr,mid,low);
}
if(arr[low] > arr[high])
{
swap(arr,low,high);
}
if(arr[mid] > arr[high])
{
swap(arr,mid,high);
}
}
int Partition(int *arr, int low, int high)// 快排一次划分算法
{
SelectPivotMedianOfThree(arr,low,high);
printf("基准数为%2d,",arr[low]);
static int n=1; // 记录排序次数
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
printf("第%d步排序结果:",n++); // 输出每步排序结果
for(int i = 0;i < 10;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
return i;
}
void InsertSort(int *arr,int low,int high)
{
int tmp ;
for(int i = low+1;i <= high;i++)
{
tmp = arr[i];
int j = i-1;
while (j >= low && arr[j] > tmp)
{
arr[j+1] = arr[j];
j--;
}
arr[j+1] = tmp;
}
}
void QuickSort(int *arr,int low,int high)
{
if(high-low+1<10) // 当整个序列的大小high-low+1<10时,采用插入排序
{
printf("\n序列大小小于10,采用直接插入排序!!\n\n");
InsertSort(arr,low,high);
return;
}
else
{
int k = Partition(arr,low,high);
if(low < k-1)
{
QuickSort(arr,low,k-1);
}
if(high > k+1)
{
QuickSort(arr,k+1,high);
}
}
}
void Show(int *arr,int len)
{
for(int i = 0;i < len;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {12,34,45,21,4,6,33,53,15,5};
int len = sizeof(arr)/sizeof(arr[0]);
printf(" 排序前:");
Show(arr,len);
QuickSort(arr,0,len-1);
printf(" 排序后:");
Show(arr,len);
}
运行结果:
#include
void Show(int *arr,int len);
void swap(int *arr,int low,int high)
{
int tmp = arr[low];
arr[low] = arr[high];
arr[high] = tmp;
}
// arr[mid] <= arr[low] <= arr[high]
void SelectPivotMedianOfThree(int *arr,int low,int high)
{
int mid = (low+high) >> 1;
if(arr[mid] > arr[low])
{
swap(arr,mid,low);
}
if(arr[low] > arr[high])
{
swap(arr,low,high);
}
if(arr[mid] > arr[high])
{
swap(arr,mid,high);
}
}
int Partition(int *arr, int low, int high)// 快排一次划分算法
{
SelectPivotMedianOfThree(arr,low,high);
printf("基准数为%2d,",arr[low]);
static int n=1; // 记录排序次数
int i = low;
int j = high;
int k = arr[low];
while (i < j)
{
while(i < j && arr[j] >= k) {j--;}
arr[i] = arr[j];
while(i < j && arr[i] <= k) {i++;}
arr[j] = arr[i];
}
arr[i] = k;
printf("第%d步排序结果:",n++); // 输出每步排序结果
for(int i = 0;i < 10;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
return i;
}
void FocusNumPar(int *arr,int low,int par,int high,int *left,int *right)
{
int parLeft = par-1;
if(low < high)
{
for(int i=par-1;i>=low;i--)
{
if(arr[i]==arr[par])
{
if(i!=par)
{
swap(arr,i,parLeft);
parLeft--;
}
else
{
i--;
}
}
}
*left = parLeft;
int parRight = par+1;
for(int i=par+1;i<=high;i++)
{
if(arr[i]==arr[par])
{
if(i!=par)
{
swap(arr,i,parRight);
parRight++;
}
else
{
i++;
}
}
}
*right = parRight;
}
}
void QuickSort(int *arr,int low,int high)
{
int left = 0;
int right = 0;
int k = Partition(arr,low,high);
FocusNumPar(arr,low,k,high,&left,&right);
if(low <= left-1)
{
QuickSort(arr,low,left);
}
if(high >= right+1)
{
QuickSort(arr,right,high);
}
}
void Show(int *arr,int len)
{
for(int i = 0;i < len;i++)
{
printf("%5d",arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = {1,4,6,7,6,6,7,6,8,6};
int len = sizeof(arr)/sizeof(arr[0]);
printf(" 排序前:");
Show(arr,len);
QuickSort(arr,0,len-1);
printf(" 排序后:");
Show(arr,len);
}
运行结果: