讲述难度从我个人角度从简单到难,从熟悉到陌生,比较重要的排序是堆排序和快速排序。
以下全都是C++代码实现,遵循的原则是最简洁。
1.冒泡排序
冒泡排序应该是最简单的排序了,高中的时候就已经练了滚瓜烂熟了。
冒泡排序的排序规则是:从最后一个数开始,每次与他上一个数进行比较,根据比较大小进行交换,接着向上移,直到遇到i,一轮比较完成,将未排序中最大(最小)的数冒泡到最上面。
vector<int> bubble_sort(vector<int> v)
{
for(int i = 0; i < v.size()-1; i++){
for(int j = v.size()-1; j > i; j--){
if(v[j] < v[j-1]){
int temp = v[j];
v[j] = v[j-1];
v[j-1] = temp;
}
}
}
return v;
}
2.选择排序
选择排序也是最基本的排序算法,也是高中和冒泡排序一起学的算法(强烈建议现在浙江高考信息技术增加快排,这两个排序太简单了,跟其他科目相比完全没有难度可言)
选择排序的排序方法是:将最左边未排序的数与后面所有数比较,选出最大(最小)的数,一轮比较完成。将最大(最小)的数移到了最左边。
vector<int> select_sort(vector<int> v)
{
for(int i = 0; i < v.size()-1; i++){
for(int j = i+1; j < v.size(); j++){
if(v[i] > v[j]){
int temp = v[j];
v[j] = v[i];
v[i] = temp;
}
}
}
return v;
}
3.插入排序
插入排序也算一种比较简单的算法,它在数据较少的时候有不错的效率,std::sort()在数据较小时使用插入排序而非快排。
算法步骤:
1)将第一待排序序列第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2)从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
vector<int> insert_sort(vector<int> v)
{
for(int i = 0; i < v.size(); i++){
int val = v[i];
int j = i;
for(; j > 0 && v[j-1] > val; j--){
v[j] = v[j-1];
}
v[j] = val;
}
return v;
}
4.希尔排序
希尔排序又叫缩小增量排序。
设定一个初始步长,对元素进行分组的插入排序,然后逐渐缩小步长,每缩小一次步长,就会对同组元素进行一次插入排序,直到步长为0。
好处:刚开始步长最大,需要插入排序的元素少,速度快;而步长很小时元素都基本有序了,需要挪动的元素少,速度快。
我们还需要知道的是,希尔排序是对直接插入排序的一个改进,可以理解成先分组再直插排序。
vector<int> shell_sort(vector<int> v)
{
int step = v.size()/2;
while(step >= 1){
for(int i = step; i < v.size(); i++){
int j = i;
int val = v[i];
for(; j >= step && v[j-step] > val; j -= step){
v[j] = v[j-step];
}
v[j] = val;
}
step /= 2;
}
return v;
}
5.归并排序
归并排序使用的是分治法,先将数组分为两部分,对两部分进行递归,这是分操作,知道数组个数为1以后执行归并再返回,依次返回执行归并操作,这是合操作。这就是分治法的思想。
void merge_sort(vector<int>& v, int l, int r)
{
if(l == r)return;
int mid = (l + r)/2;
merge_sort(v, l, mid);
merge_sort(v, mid+1, r);
merge(v, l, r, mid);
}
void merge(vector<int>& v, int l, int r, int mid)
{
vector<int> temp(r-l+1, 0);
int index = 0;
int left = l;
int right = mid+1;
while(left <= mid && right <= r){
temp[index++] = v[left] < v[right] ? v[left++] : v[right++];
}
while(left <= mid){
temp[index++] = v[left++];
}
while(right <= r){
temp[index++] = v[right++];
}
index = 0;
for(int i = l; i <= r; i++){
v[i] = temp[index++];
}
}
6.堆排序
通过前面的学习我们可以看到,如果构建一个二叉堆,最后每次从堆顶取出一个元素,那么最终取出元素就是有序的,不过如果要用来对数据按照从小到大排序,就不是构造小顶堆,而是大顶堆了,即堆顶元素大于等于其左右儿子节点。总结堆排序思路如下:
- 以O(N)时间复杂度构建N个元素的二叉堆。
- 以O(logN)时间复杂度删除一个堆顶元素,N个元素时间复杂度为O(NlogN)
- 由于删除一个堆顶元素时,就会空出一个位置,为了节省空间,将删除的堆顶元素放到数组末尾
- 当堆为空时,完成排序
- 由于数组元素从下标0开始,因此每个位置必须利用好。假设堆顶元素位置为i,那么左右儿子节点位置分别为2i+1,2i+2,父节点的位置为(i-1)/2
void heap_sort(vector<int>& v)
{
for(int i = v.size()/2-1; i >= 0; i--){
adjust_heap(v, i, v.size());
}
for(int i = v.size()-1; i > 0; i--){
int temp = v[0];
v[0] = v[i];
v[i] = temp;
adjust_heap(v, 0, i);
}
}
void adjust_heap(vector<int>& v, int root, int length)
{
int temp = v[root];
while(root*2 + 1 < length){
int child = root*2+1;
if(child + 1 < length && v[child] < v[child+1]){
child++;
}
if(temp < v[child]){
v[root] = v[child];
}else{
break;
}
root = child;
}
v[root] = temp;
}
7.快速排序
中枢的选择是一大学问
void quick_sort(vector<int>& v, int left, int right)
{
if(left >= right)
return;
int i = partition(v, left, right);
quick_sort(v, left, i-1);
quick_sort(v, i+1, right);
}
int partition(vector<int>& v, int left, int right)
{
int pivot = v[left];
int i = left+1;
int j = right;
while(1){
while(i <= j && v[i] <= pivot){i++;}
while(i <= j && v[j] >= pivot){j--;}
if(i < j){
int temp = v[i];
v[i] = v[j];
v[j] = temp;
}else{
break;
}
}
v[left] = v[j];
v[j] = pivot;
return j;
}
8.基数排序
从个位数一位一位的进行排序