算法回顾(一):排序算法(下)
快速排序
快速排序于1962年提出,其基本思想为:将数组x通过一个数t划分为两部分,使得左边部分的子数组x[l…t-1] < t,右边部分的子数组x[t+1…h] >= t,然后使用分治策略,分别对左右的子数组进行递归排序。
代码如下:
void qsort1(int *x, int len, int l, int h)
{
if (l >= h)
{
return;
}
int p = l;
int i;
for (i = l+1; i <= h; i++)
{
if (x[i] < x[l])
{
Swap(x, i, ++p);
}
}
Swap(x, l, p);
qsort1(x, len, l, p-1);
qsort1(x, len, p+1, h);
}
qsort1函数可以快速完成对随机数组进行排序,但是对于非随机的数组效果就不一定好了。最极端的情况下,n个相同元素的数组,在这个数组中,插入排序的效果非常好,时间为O(n);但是快速排序的效果就非常差,耗时O(n2)。所以将快速排序进行修改,使用两个指针,虽然提高了交换次数,但是避免了最坏情况。代码如下:
void qsort2(int *x, int len, int l, int h)
{
if (l >= h)
{
return ;
}
int t = x[l];
int i = l;
int j = h+1;
while(1)
{
// 找到从左到右第一个大于t的位置
do
{
i++;
}while(i <= h && x[i] < t);
// 找到从右到左第一个小于t的位置
do
{
j--;
}while(j >= l && x[j] > t);
if (i > j)
{
break;
}
Swap(x, i, j);
}
Swap(x, l, j);
qsort2(x, len, l, j-1);
qsort2(x, len, j+1, h);
}
堆排序
堆排序是利用堆这种数据结构进行排序。堆是一个完全二叉树,且满足堆的性质(最大堆:父节点的值大于或字节点,最小堆:父节点小于字子节点)。利用堆进行排序,需要实现:堆的创建和堆的调整,简单的算法步骤如下:
- 从数组x[0…n]中建立一个最大堆(此时数组的最大值位于x[0])
- 将x[0]和x[n]进行交换
- 调整堆,使数组x[0…(n-1)]为最大堆,将x[0]和x[n-1]进行交换,循环进行
代码如下:
//调整堆
void Heapify(int *x, int len, int k)
{
int lc = k*2 + 1;
int rc = k*2 + 2;
int maxk = k;
if (lc < len && x[lc] > x[maxk])
{
maxk = lc;
}
if (rc < len && x[rc] > x[maxk])
{
maxk = rc;
}
if (maxk != k)
{
Swap(x, k, maxk);
Heapify(x, len, maxk);
}
}
//创建堆
void BuildMaxHeap(int *x, int len)
{
int i = len/2 - 1;
for (; i >= 0; i--)
{
Heapify(x, len, i);
}
}
//堆排序
void HeapSort(int *x, int len)
{
int i;
BuildMaxHeap(x, len);
for (i = len-1; i >= 0; i--)
{
Swap(x, 0, i);
Heapify(x, i, 0);
}
}
计数排序
计数排序与之前的算法相比较思路不同,计数排序不是使用基于比较的排序方法,而是通过记录数组中每个数出现的次数,然后按照每个数在数轴上出现的先后顺序在输出出来。这个比较算法的优势是线性的排序时间,但是缺点是需要额外开辟内存空间,且该内存空间中有很多位置没有被使用到。
代码如下:
void CountSort(int *x, int len)
{
int maxv = 0;
int i = 0, j = 0;
for (i = 0; i < len; i++)
{
if (x[i] > maxv)
{
maxv = x[i];
}
}
maxv++;
int *cx = new int[maxv];
for (i = 0; i<maxv; i++)
{
cx[i] = 0;
}
for (i = 0; i < len; i++)
{
cx[x[i]]++;
}
for (i = 0; i < maxv; i++)
{
cout<<cx[i]<<" ";
while (cx[i] > 0)
{
x[j++] = i;
cx[i]--;
}
}
delete []cx;
cx = NULL;
cout<<endl;
}
桶排序
桶排序可以说是计数排序的升级版,他将数据分别映射到不同的桶中。与计数排序相比,计数排序其实也是一个个桶,但是这个桶的数目太多,从最小值到最大值。而桶排序则减少桶的数量,通过映射算法将数据分到各个桶中,但是要保证前面的桶的所有数据都比后面桶中的数据都小。
本次代码中的映射算法是使用数的百位和十位。
代码如下:
void BucketSort(int *x, int len)
{
int i, j, m=0 ;
int buckets[20][31] = {0}; //多一位,记录每个桶中已经放了多少数据
for (i = 0; i < len; i++)
{
int tmp = x[i]/10;
buckets[tmp][buckets[tmp][30]++] = x[i];
}
for (i = 0; i < 20; i++)
{
if (buckets[i][30] != 0)
{
BubbleSort(buckets[i], buckets[i][30]);
}
}
for (i = 0; i < 20; i++)
{
for (j = 0; j<buckets[i][30]; j++)
{
x[m++] = buckets[i][j];
}
}
}
基数排序
基数排序的简单思路为:首先使用数据的个位进行排序,然后使用十位进行排序,位次逐渐增高知道所有数的最高位,最后数据就变得有序了。
代码如下:
void RedixSort(int *x, int len)
{
int maxv = 0;
int i,j,k,m;
int num = 0;
int mod = 1;
int buckets[10][31] = {0};
for (i = 0; i<len; i++)
{
if (x[i] > maxv)
{
maxv = x[i];
}
}
while(maxv > 0)
{
num++;
maxv /= 10;
}
for (k = 0; k < num; k++)
{
m=0;
for (i = 0; i < len; i++)
{
int tmp = (x[i]/Power(10,k))%10;
buckets[tmp][buckets[tmp][30]++] = x[i];
}
for (i=0; i<10; i++)
{
for (j=0; j<buckets[i][30]; j++)
{
x[m++] = buckets[i][j];
}
}
memset(buckets, 0, sizeof(buckets));
}
}
至此,一些常见的排序算法就回顾完了。
参考: 十大经典排序算法(动图演示)