快速排序,是应用最广泛的排序方法。快速排序流行的原因是它实现简单、适用于各种不同的输入数据且在一般应用中比其他排序算法要快得多。 -------《算法》
一、快速排序
快速排序也是一种分治的思想。它需要将数组分为两个、四个、八个等子数组,通过递归的方法,对每个子数组进行排序。因此,快速排序的第一个步骤就是需要找到一个能将数组划分成子数组的分割点。分割点的产生与划分,需要一个简单的分割算法(Partition)来实现。
分割算法的思想为:
①先在数组中找到一个幸运数字(Lucky Number, LN)。
②将比LN小的数字(后统一称作小值)置于LN前,将比LN大的数字(后统一称作大值)置于LN之后。
首先,为代码简便,幸运数字通常可以选择数组首个元素。确定幸运数字后,需要将小值全部移动到LN前,将大值移动到LN后。因此,我们需要两个index来对数组的元素进行搜索。
具体步骤
Partition函数就是用来实现子数组的分割,需要注意的是,它的工作只限于分割,而分割后的子数组有可能仍然处于乱序当中。分割算法的具体实现:需要将两个index(我们在后面称作哨兵i和j)分别指向数组的首部和尾部,哨兵i从首部开始搜寻比LN大的元素,哨兵j从尾部开始搜寻比LN小的元素。哨兵i与j分别搜索到相应的元素之后,将这两个元素的位置进行交换。交换后,哨兵i与哨兵j继续进行相应的搜索。直到哨兵i与哨兵j相遇,分割工作的第一步就完成了。此时,我们选出的LN还位于数组的首位,需要将LN放置到数组合适的位置中去,以完成Partition函数的分割工作。在哨兵i与哨兵j相遇后,我们将LN与在哨兵j的位置的数进行交换,便可得到分割后的数组了。
Partition Function
int Partition(int *arr, int start, int end)
{
int i, j;
i = start;
j = end;
int temp1, temp2;
int LK_num = arr[i];
while (true)
{
while (arr[i] <= LK_num)
{
i++;
if (i > end)
break;
}
while (arr[j] > LK_num)
{
j--;
if (j < start)
break;
}
if (i >= j)
break;
temp1 = arr[i];
arr[i] = arr[j];
arr[j] = temp1;
}
temp2 = LK_num;
arr[start] = arr[j];
arr[j] = LK_num;
return j;
}
此外,分割函数中还存在一个问题。为了保持随机性,我们将数组通过Shuffle函数进行顺序的打乱。这样就可以随机的选择一个元素进行切分了。
Shuffle Function
void Shuffle(int *arr)
{
srand(time(NULL));
int temp;
for (int i = 0; i < SIZE; i++)
{
int value = rand() % (SIZE);
temp = arr[i];
arr[i] = arr[value];
arr[value] = temp;
}
}
在这里,也碰到了一个小问题。在使用rand()函数时,每一次运行程序后,数组的打乱顺序基本一致。这个问题在第二篇扑克牌排序的文章中也写到了。
rand()函数产生的随机数时伪随机数,是计算机按照某种规律产生的数字。因此rand()产生的数字并不是真正意义上的随机数。因此我们需要使用srand()函数来产生一个随机数种子。
因此,通过使用Partition函数,数组可达到部分有序,也被划分为两个子数组,通过对两个子数继续进行规律的分割,最后实现排序,快速排序如是而已。
#include
#include
#include
const int SIZE = 15;
using namespace std;
void Shuffle(int *arr);
void SORT(int *arr, int lo, int hi);
//void SORT(int *arr, int begin, int end);
int Partition(int *arr, int start, int end);
int main()
{
int *arr = new int[SIZE];
for (int i = 0; i < SIZE; i++)
{
cout << "第" << i + 1 << "个数为:";
cin >> arr[i];
}
cout << "数组为:";
for (int j = 0; j < SIZE; j++)
{
cout << arr[j] << " ";
}
cout << endl;
Shuffle(arr);
SORT(arr, 0, SIZE-1);
cout << "排序后的数组为: ";
for (int i = 0; i < SIZE; i++)
cout << arr[i] << " ";
cout << endl;
delete[] arr;
system("pause");
return 0;
}
void Shuffle(int *arr)
{
srand(time(NULL));
int temp;
for (int i = 0; i < SIZE; i++)
{
int value = rand() % (SIZE);
temp = arr[i];
arr[i] = arr[value];
arr[value] = temp;
}
}
int Partition(int *arr, int start, int end)
{
int i, j;
i = start;
j = end;
int temp1, temp2;
int LK_num = arr[i];
while (true)
{
while (arr[i] <= LK_num)
{
i++;
if (i > end)
break;
}
while (arr[j] > LK_num)
{
j--;
if (j < start)
break;
}
if (i >= j)
break;
temp1 = arr[i];
arr[i] = arr[j];
arr[j] = temp1;
}
temp2 = LK_num;
arr[start] = arr[j];
arr[j] = LK_num;
return j;
}
void SORT(int *arr, int lo, int hi)
{
if (lo >= hi)
return;
int LK_located = Partition(arr, lo, hi);
cout << endl;
SORT(arr, LK_located + 1, hi);
SORT(arr, lo, LK_located - 1);
}
快速排序切分方法的内循环会用一个递增的索引将数组元素和一个定值比较。这种简洁性也是快速排序的一个优点。快速排序拥有最短小的内循环。归并排序和希尔排序一般都比快速排序要慢,其原因就是他们还在内循环中移动数据。
二、快速排序算法的改进
1、和之前提及的归并排序相似,快速排序也是采用了递归的思想进行排序,改进这种递归排序的方法可以基于以下两点:
-对于分割的小数组,可以采用插入排序算法。对于小型数组,插入排序比快速排序快。
-因为快速排序采用了递归的思想,在很小的数组中,也会自己调用自己。
因此,改进的基本思想与归并算法的思想是一致的:提前结束递归,在小数组中采用更快的排序算法。
#include
#include
#include
const int SIZE = 16;
using namespace std;
void Shuffle(int *arr);
void SORT(int *arr, int lo, int hi);
void InsertSort(int * arr, int lo, int hi);
int Partition(int *arr, int start, int end);
int main()
{
int *arr = new int[SIZE];
for (int i = 0; i < SIZE; i++)
{
cout << "第" << i + 1 << "个数为:";
cin >> arr[i];
}
cout << "数组为:";
for (int j = 0; j < SIZE; j++)
{
cout << arr[j] << " ";
}
cout << endl;
Shuffle(arr);
SORT(arr, 0, SIZE-1);
cout << "排序后的数组为: ";
for (int i = 0; i < SIZE; i++)
cout << arr[i] << " ";
cout << endl;
delete[] arr;
system("pause");
return 0;
}
void Shuffle(int *arr)
{
srand(time(NULL));
int temp;
for (int i = 0; i < SIZE; i++)
{
int value = rand() % (SIZE);
temp = arr[i];
arr[i] = arr[value];
arr[value] = temp;
}
}
int Partition(int *arr, int start, int end)
{
int i, j;
i = start;
j = end;
int temp1, temp2;
int LK_num = arr[i];
while (true)
{
while (arr[i] <= LK_num)
{
i++;
if (i > end)
break;
}
while (arr[j] > LK_num)
{
j--;
if (j < start)
break;
}
if (i >= j)
break;
temp1 = arr[i];
arr[i] = arr[j];
arr[j] = temp1;
}
temp2 = LK_num;
arr[start] = arr[j];
arr[j] = LK_num;
return j;
}
void SORT(int *arr, int lo, int hi)
{
if (lo + 4 >= hi) //提前结束递归,采用插入排序
{
InsertSort(arr, lo, hi);
return;
}
int LK_located = Partition(arr, lo, hi);
cout << endl;
SORT(arr, LK_located + 1, hi);
SORT(arr, lo, LK_located - 1);
}
//引入插入排序算法
void InsertSort(int *arr, int lo, int hi)
{
int temp;
for(int i = lo; i <= hi; i++)
{
for (int j = i; j > lo; j--)
{
if (arr[j] < arr[j - 1])
{
temp = arr[j];
arr[j] = arr[j-1];
arr[j-1] = temp;
}
}
}
}
2、如果数组中存在较多相同元素时,可以采用三向切分快速排序,其思想是将比LN小的数字归于左部分,比LN大的数字归于右部分,中间部分的数是未确定部分。三向切分快速排序需要三个指针来执行操作。但总体的代码量比传统快速排序要小。
#include
#include
#include
const int SIZE = 8;
using namespace std;
void SORT(int *arr, int lo, int hi);
int main()
{
int *arr = new int[SIZE];
for (int i = 0; i < SIZE; i++)
{
cout << "第" << i + 1 << "个数为:";
cin >> arr[i];
}
cout << "数组为:";
for (int j = 0; j < SIZE; j++)
{
cout << arr[j] << " ";
}
cout << endl;
SORT(arr, 0, SIZE-1);
cout << "排序后的数组为: ";
for (int i = 0; i < SIZE; i++)
cout << arr[i] << " ";
cout << endl;
delete[] arr;
system("pause");
return 0;
}
void SORT(int *arr, int lo, int hi)
{
int lt = lo;
int gt = hi;
int i = lo + 1;
int v = arr[lo];
int temp;
if (lo >= hi)
return;
while (i <= hi)
{
if (arr[i] < v)
{
temp = arr[i];
arr[i] = arr[lt];
arr[lt] = temp;
i++;
lt++;
}
if (arr[i] > v)
{
temp = arr[i];
arr[i] = arr[gt];
arr[gt] = temp;
gt--;
}
if (arr[i] == v)
{
i++;
}
}
SORT(arr, lo, lt - 1);
SORT(arr, gt + 1, hi);
}