快速排序:(时间复杂度O(N^2))
方法一:左右指针法(递归,划分为子问题)
注:end的起始位置就是key的位置
步骤:
1.从数列中挑出一个元素,key
2.重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
3.递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。
有序的情况下或每次选出的key最大or最小,快速排序最坏。
优化:三数取中(取大小中间的那个数,这个方法是为了防止有序的情况下,快排最坏) //减少建立栈帧的开销==
保证每次选的key不是最大或最小。
优化:小区间优化(小区间使用插入排序)//减少建立栈帧的开销
小区间使用插入排序,消耗小
方法二:挖坑法
初始状态:
移动状态:
begin找大,找到大后,值给给end,然后坑变成begin
end找小,找到小后,值填到坑上去,然后坑变成end
方法三:(可以使单链表走快排,这个方法代码简洁,但更难理解)
使用两个指针,prev指向区间的前一个位置,cur指向这段区间的开始位置。
初始状态:
移动状态:
key交换:
非递归快速排序: 栈..
代码:
快速排序
Sort.h
#pragma once
//方法三
int PartSort3(int*a, int left, int right)
{
int prev = left - 1, cur = left;
while (cur < right)
{
if (a[cur] < a[right] && ++prev != cur)
{
swap(a[cur], a[prev]);
}
++cur;
}
swap(a[right], a[++prev]);
return prev;
}
//方法二:挖坑法
int PartSort2(int*a, int begin, int end)
{
int key = a[end];
while (begin < end)
{
while (begin < end && a[begin] <= key)
{
++begin;
}
a[end] = a[begin];
while (begin < end && a[end] >= key)
{
--end;
}
a[begin] = a[end];
}
a[begin] = key;//begin和end相遇
return begin;
}
//三数取中法
int GetMidIndex(int* a, int begin, int end)
{
int mid = begin + ((end - begin)) >> 1;
if (a[begin] < a[mid])
{
if (a[mid] < a[end])
{
return mid;
}
else if (a[begin] > a[end])
{
return begin;
}
else
{
return end;
}
}
else //mid <= begin
{
if (a[end] < a[mid])
{
return mid;
}
else if (a[begin] < a[end])
{
return begin;
}
else
{
return end;
}
}
}
//方法一;左右指针法
int PartSort1(int*a, int begin, int end) //部分排序
{
int mid = GetMidIndex(a, begin, end);
swap(a[mid], a[end]); //将中位数换到最后一个,作为key
int key = end;//end的起始位置就是key的位置
while (begin < end)
{
while (begin < end && a[begin] <= a[key]) //begin找大,跟key相等 放左边
{
++begin;
}
while (begin < end && a[end] >= a[key]) //end找小,跟key相等 放右边
{
--end;
}
if (begin < end)
{
swap(a[begin], a[end]);
}
}
swap(a[begin], a[key]); //相等时与key交换
return begin;
}
//[left,right]假设区间为闭区间
void QuickSort(int* a,int left,int right)
{
if (left >= right)
{
return;
}
if (right - left < 5)//right - left是区间个数,如果小于5个,使用插入排序(小区间优化)
{
InsertSort(a+left, right - left + 1);//区间是从a+left开始 right - left + 1结束
}
else //说明这个区间至少有两个及以上的数 (left < right)
{
//PartSort1为左右指针法,PartSort2为挖坑法(使用PartSort2时要将小区间优化注释掉),PartSort3为方法三
int div = PartSort3(a, left, right);//div为划分的分区
QuickSort(a, left, div - 1);//左区间 [left, div - 1]
QuickSort(a, div + 1, right);//右区间 [div + 1, right]
}
}
void TestQuickSort()
{
int a[] = { 2, 5, 4, 9, 3, 6, 1, 7, 1, 0 }; //有相等的情况
//int a[] = { 0, 1 };//注意,如果end的起始位置为key的前一个,则这种情况下会出错
//int a[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };//有序的情况下,三数取中好
QuickSort(a, 0,sizeof(a) / sizeof(a[0])-1);//给出区间
PrintArray(a, sizeof(a) / sizeof(a[0]));
}
Test.cpp
#include<iostream>
#include<assert.h>
using namespace std;
#include"Sort.h"
int main()
{
TestQuickSort();
system("pause");
return 0;
}
非递归 快速排序
Sort.h
#pragma once
#include<stack>
int PartSort1(int*a, int begin, int end) //部分排序
{
int mid = GetMidIndex(a, begin, end);
swap(a[mid], a[end]); //将中位数换到最后一个,作为key
int key = end;//end的起始位置就是key的位置
while (begin < end)
{
while (begin < end && a[begin] <= a[key]) //begin找大,跟key相等 放左边
{
++begin;
}
while (begin < end && a[end] >= a[key]) //end找小,跟key相等 放右边
{
--end;
}
if (begin < end)
{
swap(a[begin], a[end]);
}
}
swap(a[begin], a[key]); //相等时与key交换
return begin;
}
//非递归快速排序
void QuickSortNonR(int* a, int left, int right)
{
stack<int> s;
s.push(right);
s.push(left);
while (!s.empty())
{
int begin = s.top();
s.pop();
int end = s.top();
s.pop();
int div = PartSort1(a, begin, end);//[begin,div-1] [div+1,end]
if (begin < div - 1)
{
s.push(div - 1);
s.push(begin);
}
if (div + 1 < end)
{
s.push(end);
s.push(div +1);
}
}
}
void TestQuickSort()
{
int a[] = { 2, 5, 4, 9, 3, 6, 1, 7, 1, 0 };
QuickSortNonR(a, 0, sizeof(a) / sizeof(a[0]) - 1);
PrintArray(a, sizeof(a) / sizeof(a[0]));
}
Test.cpp
#include<iostream>
#include<assert.h>
using namespace std;
#include"Sort.h"
int main()
{
TestQuickSort();
system("pause");
return 0;
}