好吧,我现在没有网,我现在就只能做一下这些线下的工作
那我准备在下午完成排序算法和STL的整理
数据结构也基本上就只差排序和那个字符串的KMP算法了
排序算法的稳定性:
如果元素中有两个元素k1, k2,排序之前k1排在k2前面,排序之后k1也是排在k2前面,则称排序是稳定的
内排序和外排序
内排序就是说排序过程中数据都在内存里,不用来回交换;外排序就是数据在内存一部分在外存一部分啦
性能评估
数据比较次数和数据移动次数
靠 我刚写了几行字就有网了,好吧,那也得等我写完这一篇吧
核心思想:插入第i个元素时,前面i - 1个元素都已经排好序了
```C++
void insert_sort(vector& v)
{
int n = v.size();
for(int i = 1; i < n; i++)
{
for(int j = 0; j < i; j++)
{
if(v[i] < v[j])
{
int k = i;
int tmp = v[i];
while(k > j)
{
v[k] = v[k - 1];
k--;
}
v[j] = tmp;
break;
}
}
}
}
```
时间复杂度O(n^2)
在插入第i个元素时,前i - 1个元素都已经排好序, 利用折半查找的思路查找v[i]的插入位置
也就是将直接插入排序的查找比较过程变成了折半查找
```C++
void binary_insert_sort(vector& v)
{
int n = v.size();
for(int i = 1; i < n; i++)
{
int left = 0;
int right = i - 1;
int mid = (left + right) / 2;
int tmp = v[i];
while(left <= right)
{
mid = (left + right) / 2;
if(tmp < v[mid])
right = mid - 1;
else if(tmp > v[mid])
left = mid + 1;
else
{
left = mid + 1;
break;
}
}
for(int j = i - 1; j >= left; j--)
v[j + 1] = v[j];
v[left] = tmp;
}
}
```
折半插入排序也是稳定的
时间复杂度为O(n * logn)
核心思想:交换不相邻元素以对数组进行局部排序,再使用插入排序使局部有序的数组有序(gap = 1)
使用一个gap作为间隔,用插入排序是得每个间隔i, i + gap, i + 2 * gap有序
随着gap减少到1,就可以变成整个数组插入排序(但是很多已经局部有序)
为什么选择插入排序呢?因为基本上已经局部有序了,插入排序在最好情况下为O(n),且数量较少时较为有效
```C++
void shell_sort(vector& v)
{
int n = v.size();
int gap = n - 1;
do
{
gap = gap / 3 + 1;
for(int i = gap; i < n; i++)
{
for(int j = i; j >= gap; j -= gap)
{
if(v[j] < v[j - gap])
{
int tmp = v[j - gap];
v[j - gap] = v[j];
v[j] = tmp;
}
}
}
}while(gap > 1);
}
```
无法证明,但对任意数组表现也不错,小于O(n^2),不稳定排序
非常简单的一种时间复杂度为O(n^2)的排序算法
核心思想就是:第一层循环表示多少趟, 第二层循环表示从哪到哪两两交换,如果前面比后面大,就交换(也就是把大的换到后面)
```C++
void bubble_sort(vector& 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])
{
int tmp = a[j + 1];
a[j + 1] = a[j];
a[j] = tmp;
}
}
}
}
```
也是稳定的
```C++
void bubble_sort(vector& a)
{
int n = a.size();
for(int i = 0; i < n; i++)
{
int flag = false;
for(int j = 0; j < n - 1 - i; j++)
{
if(a[j] > a[j + 1])
{
int tmp = a[j + 1];
a[j + 1] = a[j];
a[j] = tmp;
flag = true;
}
}
if(!flag)return;
}
}
```
改进就是设置一个标志,如果这一遍冒泡没有交换任何元素,说明已经有序了,那就直接返回就好了
典型的分治法, 选一个基准点(随便选), 通过移动使得基准点左边都是比他小的,右边都是比他大的,然后分别进入两边再排序.
对于移动元素, 快速排序是冒泡排序的改进, 不是通过不断比较交换的(相邻交换), 是直接将两个交换(跳跃交换), 但是我们又不需要把基准点赋值,最后赋值即可.
```C++
int partition(vector& v, int low, int high)
{
int key = v[low];//基准点选最左边
while(low < high)
{
while(low < high && v[high] >= key) high--; //从右边开始, 右边大于key的就过掉
if(v[high] < key) //如果有小于key的就和左边未检查的部分交换,但是其实我们不需要真的把左边的赋值过来,后面会处理的
{
v[low] = v[high];
low++;
}
while(low < high && v[low] <= key) low++;//同样,再过掉左边比key小的
if(v[low] > key)//找到一个大的,就交换到右边未处理的那
{
v[high] = v[low];
high--;
}
}
v[low] = key;//结束的时候low==high,就是key应该在的位置
return low;
}
//另一个更好理解的版本
int partition(vector& v, int low, int high)
{
int keypos = low;
int key = v[low];
for(int i = low + 1; i < high; i++)
{
if(v[i] < key)
{
keypos++;//keypos已左都是小于key的,如果小于key,我们就把v[i]放在keypos上,把原来那些大于未处理的交换到后面去
if(keypos != i)
{
int tmp = v[i];
v[i] = v[keypos];
v[keypos] = tmp;
}
}
}
v[low] = v[keypos];
v[keypos] = key;
return keypos;
}
void quick_sort(vector& v, int low, int high)
{
if(low < high)
{
int k = partition(v, low, high);
quick_sort(v, low, k - 1);
quick_sort(v, k + 1, high);
}
}
```
快速排序也是不稳定的, 时间复杂度为O(nlogn), 是性能最好的一种内排序方法
时间复杂度取决于递归树的高度,最好情况是完全二叉树,最坏情况是单链树
快速排序的运行时间取决于输入序列, 对n较大的平均情况下, 快速排序效果较好, 对于n较小的情况下, 快排效果比简单排序效果差
我们上面说到如果规模很小时,使用直接插入排序是一个很好的办法,所有我们可以设置在high - low < M时选择直接插入排序;或者说我们小于一定规模就返回,这样最终得到的是一个基本上已经排好序的数组,我们再最后调用一遍直接插入排序就好了
还有基准元素的选择也对快速排序有影响,我们最希望的是左右平分, 基准元素位于中间,因此我们可以随机选择基准元素.
核心思想:选出未排序中最小的不断放在未排序的后面
```C++
void select_sort(vector& v)
{
for(int i = 0; i < v.size() - 1; i++)
{
int pos = i;
for(int j = i + 1; j < v.size(); j++)
{
if(v[j] < v[pos])
pos = j;
}
int tmp = v[pos];
v[pos] = v[i];
v[i] = tmp;
}
}
```
不稳定排序算法, 时间复杂度也是O(n^2)
见堆
归并排序和快速排序类似, 也是基于分治法的, 将序列分成两个长度相等的子序列,对子序列进行排序,然后再将其合并.
归并排序不依赖输入序列, 避免了快排的最坏情况
所以, 归并排序主要包括两步: 划分 合并(合并两个有序表)
```C++
void merge(vector& v, vector& s, int left, int mid, int right)
{
for(int i = 0; i < v.size(); i++)
s[i] = v[i];
int p1 = left, p2 = mid + 1, p = left;
while(p1 <= mid && p2 <= right)
{
if(s[p1] < s[p2])
v[p++] =s[p1++];
else
v[p++] = s[p2++];
}
while(p1 <= mid)
v[p++] = s[p1++];
while(p2 <= right)
v[p++] = s[p2++];
}
void merge_sort(vector& v, vector& s, int left, int right)
{
if(left >= right) return;
int mid = (left + right) / 2;
merge_sort(v, s, left, mid);
merge_sort(v, s, mid + 1, right);
merge(v, s, left, mid, right);
}
```
归并排序是一种稳定排序方法, 时间复杂度为O(nlogn), 空间复杂度需要一个O(n)的辅助数组
直接插入排序最好情况下能够只比较n - 1次,不用移动
改进的冒泡排序最好情况下, 也只比较n - 1次即可
选择排序比较次数与初始序列无关,都需要比较n^2次, 但是元素移动与初始有关
快排就是与选择的基准元素有关,归并排序就与初始序列无关
希尔排序介于简单排序和高效排序之间
稳定的算法有:冒泡、直接插入、折半插入、归并