选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。
1.快速排序
快速排序的地位在业界是不言而喻的,时间复杂度为O(nlogn),且常系数为2。在不考虑极端情况下,速度是很理想的。
(1)确定基准数,利用partition函数得到基准数在一趟排序后的位置。
int partition(vector<int> &vec, int low, int high)
{
int i = low;
int j = high;
int index = i;
int x = vec[index]; //确定基准数
while ( i < j)
{
//从后向前找,找到一个小于基准数的进行交换
while (i < j && vec[j] >= x)
j--;
if (i < j)
{
vec[i] = vec[j];
i++;
}
//从前向后找,找到一个大于基准数的进行交换
while(i < j && vec[i] < x)
i++;
if(i < j)
{
vec[j] = vec[i];
j--;
}
}
vec[i] = x;
return i;
}
另外一种实现方法
int partition(vector<int>& vec,int left,int right)
{
//int i = left + rand()%(right -left +1);
//swap(&vec[i], &vec[right]);
int pivot = vec[right];
int index = left;
for(int i = left; i < right; i++)
{
if(vec[i] < pivot)
{
swap(&vec[i],&vec[index]);
index++;
}
}
swap(&vec[right],&vec[index]);
return index;
}
(2)分治(递归)进行上述操作,得到最终排序序列。
void quick_sort_core(vector<int> &vec,int low,int high)
{
if (low < high)
{
int index = partition(vec,low,high);
quick_sort_core(vec,low,index-1);
quick_sort_core(vec,index+1,high);
}
}
随机快速排序
对数学比较感兴趣的同学可以看下随机快速排序时间复杂度的证明,同样是O(nlogn)。
与普通快速排序相比,只需把partititon函数中index通过如下函数获取。
int random_index(int low,int high)
{
srand(time(NULL));
return low + rand()%(high - low + 1);
}
常见的优化思路
(1)基准值的确定。可以通过如些操作降低递归次数,提高效率:通过比较第一个值、中间值、最后一个值,得到中间大小的值作为基准值,从而保证一趟快速排序很快达到平衡。
(2)在快速排序算法内部,内置插入排序算法,当未排序的数字个数小于6(印象中是这个值)的时候调用插入排序算法进行排序。
下面的冒牌排序、推排序,以Top K问题作为示例
2.冒泡排序
时间复杂度O(Kn),思路不错,需要把数据一次读进内存
void bubble_sort(vector<int> vec,int k)
{
int index = 0;
for (int i = 0; i < vec.size()-1; i++)
{
if (index < k)
{
for(int j = i+1; j < vec.size(); j++)
{
if (vec[i] > vec[j])
{
swap(&vec[i],&vec[j]);
}
}
}
else
break;
}
output_vec(vec,k);
}
3.堆排序
注:最终取得Top K小值,需要使用最大堆
(1)下面是基本堆的一些操作实现
//最大堆
void adjust_heap(vector<int> &vec,int i,int n)
{
int tmp;
int child;
for (tmp = vec[i]; 2*i+1 < n; i = child)
{
child = 2*i+1;
if(child != n-1 && vec[child] < vec[child+1]) //找到更大的子节点
child++;
if (tmp < vec[child]) //满足条件则交换
vec[i] = vec[child];
else
break;
}
vec[i] = tmp;
}
void push_heap(vector<int> &vec,int num)
{
vec.push_back(num);
int i;
for (i = vec.size()-1; i>0 && num > vec[(i-1)/2]; i = (i-1)/2)
vec[i] = vec[(i-1)/2];
vec[i] = num;
}
void pop_heap(vector<int> &vec) //int &max
{
if(vec.empty())
return;
//int max = vec[0];
vec[0] = vec[vec.size()-1];
vec.resize(vec.size()-1);
adjust_heap(vec,0,vec.size());
}
void make_heap(vector<int>& vec)
{
for(int i = vec.size()/2; i >= 0;i--)
adjust_heap(vec,i,vec.size());
}
void sort_heap(vector<int>& vec)
{
for(int i = vec.size()-1; i>0 ; i--)
{
swap(&vec[0],&vec[i]);
adjust_heap(vec,0,i);
}
}
(2)利用如上的数据结构,可以得到Top K数据。时间复杂度O(nlogk),不需要一次读进内存
vector<int> need_sort_vec;
for (i = 0; i < vec.size(); i++)
{
if(i < k-1)
{
cout << i << endl;
need_sort_vec.push_back(vec[i]);
}
else if(i == k-1)
{
need_sort_vec.push_back(vec[i]);
make_heap(need_sort_vec);
}
else
{
push_heap(need_sort_vec,vec[i]);
pop_heap(need_sort_vec);
}
}
sort_heap(need_sort_vec);
output_vec(need_sort_vec,need_sort_vec.size());
4.归并排序
时间复杂度为O(nlogn),是一种稳定的基于比较排序算法
分解任务一
将有二个有序数列vec[first…mid]和vec[mid…last]合并。
void merge_array(vector<int> &vec,int first,int mid,int last,vector<int> &tmp)
{
int i = first, j = mid+1;
int k = 0;
while (i <= mid && j <= last)
{
if(vec[i] < vec[j])
tmp[k++] = vec[i++];
else
{
tmp[k++] = vec[j++];
//vec[j]和前面每一个数都能组成逆序数对
inverse_pairs_count += mid - i + 1;
}
}
while (i <= mid)
tmp[k++] = vec[i++];
while(j <= last)
tmp[k++] = vec[j++];
//将排好序的tmp赋值给vec
for (i = 0;i < k; i++)
vec[first+i] = tmp[i];
分解任务二
分治法
void merge_sort_core(vector<int>& vec,int first,int last,vector<int>& tmp)
{
if (first < last)
{
int mid = first + (last - first)/2;
merge_sort_core(vec,first,mid,tmp); //左边排好序
merge_sort_core(vec,mid+1,last,tmp); //右边排好序
merge_array(vec,first,mid,last,tmp); //再将两个有序数列排序
}
}
逆序对与归并排序的关系
在进行有序序列合并过程中,一旦出现后面的数据比前面的小,则出现逆序对。
只需添加一行代码即可。
while (i <= mid && j <= last)
{
if(vec[i] < vec[j])
tmp[k++] = vec[i++];
else
{
tmp[k++] = vec[j++];
//vec[j]和前面每一个数都能组成逆序数对
inverse_pairs_count += mid - i + 1;
}
}