排序算法
本篇博客主要粗略的介绍几种常见的排序算法:冒泡排序、选择排序、插入排序、归并排序、快速排序、希尔排序、堆排序、(计数排序、桶排序、基数排序这几种本章没有,我不会(我是蒟蒻),大家想学可以去看看别的博客)。代码均用c++展示。
1. 冒泡排序
冒泡排序是一种简单的排序算法。它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。走访数列的工作是重复地进行直到没有再需要交换,也就是说该数列已经排序完成。这个算法的名字由来是因为越小的元素会经由交换慢慢“浮”到数列的顶端。
算法描述
比较相邻的元素。如果第一个比第二个大,就交换它们两个;
对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对,这样在最后的元素应该会是最大的数;
针对所有的元素重复以上的步骤,除了最后一个;
重复步骤1~3,直到排序完成。
冒泡排序的算法时间复杂度最好情况下是O(1)<注意:接下来我们介绍时间复杂度都是介绍最坏情况>,也就是所有的都是有序的,最坏的情况下是O(n2)。
算法代码:
void bubblesort(vector<int>& a)
{
int n = a.size();
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n - 1 - i; j++)
{
if (a[j] > a[j+1])
swap(a[j], a[j+1]);
}
}
2.选择排序
选择排序是一种简单直观的排序算法,它也是一种交换排序算法,和冒泡排序有一定的相似度,可以认为选择排序是冒泡排序的一种改进。
算法描述
在未排序序列中找到最小(大)元素,存放到排序序列的起始位置
从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的末尾。
重复第二步,直到所有元素均排序完毕。
时间复杂度为O(n2)。
算法代码
void select_sort(vector<int>& vt)
{
for (int i = 0; i < vt.size() - 1; i ++)
{
int swap_pos = i;
for (int j = i + 1; j < vt.size(); j++)
{
if (vt[swap_pos] > vt[j])
{
swap_pos = j;
}
}
if (swap_pos != i)
{
swap(vt[swap_pos], vt[i]);
}
}
}
3.插入排序
插入排序是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。
算法描述
把待排序的数组分成已排序和未排序两部分,初始的时候把第一个元素认为是已排好序的。
从第二个元素开始,在已排好序的子数组中寻找到该元素合适的位置并插入该位置。
重复上述过程直到最后一个元素被插入有序子数组中。
时间复杂度O( n2 )。
实现代码
void insertsort(vector<int>& a)
{
int n = a.size();
for (int i = 1; i < n; i++)
{
int insert_num = a[i], j;
for (j = i - 1; j >= 0; j--)
{
if (a[j] > insert_num)
a[j + 1] = a[j];
else
break;
}
a[j + 1] = insert_num;
}
}
并归排序
归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为2-路归并。
算法描述
两种方法
递归法
申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
设定两个指针,最初位置分别为两个已经排序序列的起始位置
比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
重复步骤3直到某一指针到达序列尾
将另一序列剩下的所有元素直接复制到合并序列尾
迭代法
原理如下(假设序列共有n个元素):
将序列每相邻两个数字进行归并操作,形成ceil(n/2)个序列,排序后每个序列包含两/一个元素
若此时序列数不是1个则将上述序列再次归并,形成ceil(n/4)个序列,每个序列包含四/三个元素
重复步骤2,直到所有元素排序完毕,即序列数为1
时间复杂度为O(1)。
代码实现
vector<int> mergeHelper(vector<int> &a, int left, int right)
{
if (left == right) return vector<int> (1, a[left]);
int mid = (right - left) / 2 + left;
vector<int> l = mergeHelper(a, left, mid);
vector<int> r = mergeHelper(a, mid + 1, right);
//merge
vector<int> ret;
int ll = 0, rr = 0;
while (ll < l.size() && rr < r.size())
{
if (l[ll] <= r[rr]) ret.push_back(l[ll++]);
else ret.push_back(r[rr++]);
}
while (ll < l.size()) ret.push_back(l[ll++]);
while (rr < r.size()) ret.push_back(r[rr++]);
return ret;
}
void mergesort(vector<int>& a)
{
a = mergeHelper(a, 0, a.size() - 1);
}
5. 快速排序
快速排序是一个知名度极高的排序算法,其对于大数据的优秀排序性能和相同复杂度算法中相对简单的实现使它注定得到比其他算法更多的宠爱。
算法描述
从数列中挑出一个元素,称为"基准",
重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作。
递归地把小于基准值元素的子数列和大于基准值元素的子数列排序。
因为快排是一种不稳定的排序。时间复杂度我也不知道咋讲,大家百度一下。
接下来分享两种快排的代码
void quicksortHelper(vector<int>& a, int start, int end)
{
if (start >= end) return;
int l = start, r = end;
int pivot = a[(end - start) / 2 + start];
while (l <= r)
{
while (l <= r && a[r] > pivot) r--;
while (l <= r && a[l] < pivot) l++;
if (l <= r) swap(a[l++], a[r--]);
}
quicksortHelper(a, start, r);
quicksortHelper(a, l, end);
}
void quicksort(vector<int>& a)
{
quicksortHelper(a, 0, a.size() - 1);
}
int findmiddle(int a, int b, int c)
{
if (a >= b && a <= c)
return a;
else if (b >= a && b <= c)
return b;
else
return c;
}
void quicksortHelper(vector<int>& a, int start, int end)
{
if (start >= end) return;
int l = start, r = end;
int pivot = findmiddle(a[start], a[end], a[(end - start) / 2 + start]);
while (l <= r)
{
while (l <= r && a[r] > pivot) r--;
while (l <= r && a[l] < pivot) l++;
if (l <= r) swap(a[l++], a[r--]);
}
quicksortHelper(a, start, r);
quicksortHelper(a, l, end);
}
void quicksort(vector<int>& a)
{
quicksortHelper(a, 0, a.size() - 1);
}
6.希尔排序(插入排序的改良版)
在希尔排序出现之前,计算机界普遍存在“排序算法不可能突破O(n2)”的观点。希尔排序是第一个突破O(n2)的排序算法,它是简单插入排序的改进版。希尔排序的提出,主要基于以下两点:
插入排序算法在数组基本有序的情况下,可以近似达到O(n)复杂度,效率极高。
但插入排序每次只能将数据移动一位,在数组较大且基本无序的情况下性能会迅速恶化。
算法描述
先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,具体算法描述:
选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
按增量序列个数k,对序列进行 k 趟排序;
每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
时间复杂度也是不确定的
代码展示
void shellsort(vector<int>& a)
{
int n = a.size();
for (int increment = n / 2; increment > 0; increment /= 2)
{
for (int i = increment; i < n; i++)
{
int insert_num = a[i], j;
for (j = i - increment; j >= 0; j -= increment)
{
if (a[j] > insert_num)
a[j + increment] = a[j];
else
break;
}
a[j + increment] = insert_num;
}
}
}
7.堆排序
堆排序是指利用堆积树(堆)这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素。堆排序就是把最大堆堆顶的最大数取出,将剩余的堆继续调整为最大堆,再次将堆顶的最大数取出,这个过程持续到剩余数只有一个时结束。
算法描述
建立最大堆,建堆的过程是从N/2的位置开始,将父节点与子节点比较,如果子节点大于父节点则交换。为什么是N/2,是因为堆中树叶的个数是N/2。
从堆中删除堆顶元素,对于最大堆而言,堆顶元素也就是最大元素。每删除一个堆顶元素,就将堆顶元素放在数组的后面,因为每删除一个就出现一个空位,所以数组后面是有地方存放的。
进行N-1次的删除以后,整个数组就是排序的状态了。
代码展示
void make_heap(vector<int>& a, int size) //size的当前堆的大小,也就是数组的前size个数
{
for (int i = size - 1; i > 0; i--)
{
if (i % 2 && a[i] > a[(i - 1) / 2])//奇数
swap(a[i], a[(i - 1) / 2]);
else if (i % 2 == 0 && a[i] > a[(i - 2) / 2])//偶数
swap(a[i], a[(i - 2) / 2]);
}
}
void heapsort(vector<int>& a)
{
int n = a.size();
while (n)
{
make_heap(a, n); //每次把新的最大元素移到堆顶,也就是a[0]
n--;
swap(a[0], a[n]); //然后把当前最大移动到后面来作为排好序的元素
}
}
好了以上就是本节类容了,本蒟蒻就会这么多了。