1. 常用经典排序算法
冒泡排序、插入排序、选择排序; O(n^2)
快速排序、归并排序; O(nlogn)
计数排序、基数排序、桶排序; O(n)
归并排序和快速排序
。这两种排序算法适合大规模
的数据排序,比上一节讲的那三种排序算法要更常用(上一节排序算法更适合小规模
的数据排序)。
分治思想: 分而治之, 将一个大问题分解成小的子问题。
1. 归并排序
(1). 将数组从中间分成前后两个部分;
(2). 然后对前后两部分分别排序,再将排好序的两部分合并在一起;
分治算法一般用递归来实现,分治是一种解决问题的处理思想,递归是一种编程技巧。
递归的书写技巧,分析得出递推公式,然后找到终止条件
- 源代码案例
void mergesort(int* a, int s, int n)
{
if(s>=n) return;
if(a == NULL || n <2) return;
int mid = (n+s)/2;
mergesort(a, s, mid);
mergesort(a, mid+1, n);
merge(a, s, mid, n);
}
void merge(int* a, int s, int mid, int n)
{
int help[n-s+1];
int j=0;
int lindex = s;
int rindex = mid+1;
// 判断语句"=="等号边界.
while(lindex <= mid && rindex <= n)
{
/************
* 涉及递归的时间复杂度分析稍微比较复杂(且空间复杂度不能像时间复杂度一样分析):
* 对n个元素排序消耗的时间T(n) = 2*T(n/2)+K, K-为合并两个子问题的时间消耗; T(n) = 2*(2*T(n/4)+n/2)+K = ... = 2^k * T(n/2^k) +K*n
* 令T(n/2^k) = T(1)(一个元素的排序的时间消耗为常数C) --> k =logn;
* 所以: T(n) = nlogn+Cn;
* 归并排序:, 时间复杂度为O(nlogn), 空间复杂度为O(n)(所以归并排序不是原地排序),是稳定排序。
*
* 1. 将数组从中间开始分为两个部分,依次递归;
* 2. 分别对分割的数组的分别排序, 最后分割的数组一般为一个或者两个;
* 3. 将分割好的有序数组,互相比较大小,从小的开始插入到新的数组,进行合并;
* ********************/
help[j++] = (a[lindex] < a[rindex]) ? a[lindex++] : a[rindex++];
}
while(lindex <= mid)
{
help[j++] = a[lindex++];
}
while(rindex <= n)
{
help[j++] = a[rindex++];
}
// 将辅助数组的数据copy更新到原数组数据中.
for(int i=0; i< n-s+1; i++)
{
// 注意这里的左右边界不要认为"s=0, n=数组的长度,就省略为以下形式"
/***
* for(int i=0; i< n; i++) {a[i] = help[i];}
* 这样是错误的, 因为函数是递归调用, 所以数据分段式的组合应满足函数的实际长度.
*/
a[s+i] = help[i];
}
}
2. 快速排序
分治的思想
核心思路:
(1). 假设要排序数组[p,r]的数据, 首先选择p-r之间的任意一个数据作为分区点(pivot)
;
(2). 然后遍历数组的数据, 将小于pivot
放于左边,将大于pivot
放于右边;
(3). 然后根据分治、递归的思想,不断排序ppivot-1和pivot+1r两个区间;
- 源代码案例
void quicksort(int* a, int s, int n)
{
if(a == NULL || n<2) return;
if(s >= n ) return;
int povit = partition(a, s, n);
quicksort(a, s, povit-1);
quicksort(a, povit+1, n);
}
int partition(int* a, int s, int n )
{
int q = a[n];
int i = s;
/****
* 快速排序: 时间复杂度分析(递归树分析(暂时不谈),归并是递推公式分析)O(nlogn), 空间复杂度为O(1)(属于原地排序); 不稳定排序(相同的元素的先后顺序会发生改变)
* 1. 分区时, 给出两个索引都指向初始位置, 并设置初始分区点;
* 2. 将一个索引遍历所有数组数据(除掉分区点), 然后将其与分区点数据进行对比;
* 3. 将小于pivot的数据移动到左边, 大于的数不动(实际上当最后pivot与最后一个小的数交换位置时, 大数会自动排在pviot的后面);
* 4. 最后将pivot对应的数移到中间;
* **********************************/
for(int j=s; j<=n-1; j++)
{
if(a[j] < q)
{
swap(a[i], a[j]);
i++;
}
}
swap(a[i], a[n]); // 将分区点的数转换到小数与大数的中间,将前后分隔开
return i;
}
3.最后类似上一节,给出主函数测试代码,供初学者参考
int main()
{
int a[] = {4,5,6,1,3,2};
std::cout<<"Before Sort Data: "<<std::endl;
for(int i =0; i<6; i++)
{
cout<<a[i]<<",";
}
cout<<endl;
solutions solve;
//solve.mergesort(a, 0, 5);
solve.quicksort(a, 0, 5);
std::cout<<"After Sort Data: "<<std::endl;
for(int i =0; i<6; i++)
{
cout<<a[i]<<",";
}
cout<<endl;
}