DS:排序

好吧,我现在没有网,我现在就只能做一下这些线下的工作

那我准备在下午完成排序算法和STL的整理

数据结构也基本上就只差排序和那个字符串的KMP算法了

1 排序算法

排序算法的稳定性

如果元素中有两个元素k1, k2,排序之前k1排在k2前面,排序之后k1也是排在k2前面,则称排序是稳定的

内排序和外排序

内排序就是说排序过程中数据都在内存里,不用来回交换;外排序就是数据在内存一部分在外存一部分啦

性能评估

数据比较次数和数据移动次数

靠 我刚写了几行字就有网了,好吧,那也得等我写完这一篇吧

2 插入排序

2.1 直接插入排序

核心思想:插入第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)

2.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)

2.3 希尔排序

核心思想:交换不相邻元素以对数组进行局部排序,再使用插入排序使局部有序的数组有序(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);
}
```

举个例子就理解了:
DS:排序_第1张图片

无法证明,但对任意数组表现也不错,小于O(n^2),不稳定排序

3 交换排序

3.1 冒泡排序

非常简单的一种时间复杂度为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;
    }
}
```

改进就是设置一个标志,如果这一遍冒泡没有交换任何元素,说明已经有序了,那就直接返回就好了

3.2 快速排序

典型的分治法, 选一个基准点(随便选), 通过移动使得基准点左边都是比他小的,右边都是比他大的,然后分别进入两边再排序.

对于移动元素, 快速排序是冒泡排序的改进, 不是通过不断比较交换的(相邻交换), 是直接将两个交换(跳跃交换), 但是我们又不需要把基准点赋值,最后赋值即可.

```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时选择直接插入排序;或者说我们小于一定规模就返回,这样最终得到的是一个基本上已经排好序的数组,我们再最后调用一遍直接插入排序就好了

还有基准元素的选择也对快速排序有影响,我们最希望的是左右平分, 基准元素位于中间,因此我们可以随机选择基准元素.

4 选择排序

4.1 简单选择排序

核心思想:选出未排序中最小的不断放在未排序的后面

```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)

4.2 堆排序

见堆

5 归并排序

归并排序和快速排序类似, 也是基于分治法的, 将序列分成两个长度相等的子序列,对子序列进行排序,然后再将其合并.

归并排序不依赖输入序列, 避免了快排的最坏情况

所以, 归并排序主要包括两步: 划分 合并(合并两个有序表)

```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)的辅助数组

6 总结

  • 简单排序方法主要包括直接插入排序、冒泡排序、选择排序, 他们适合数量较小的序列,时间复杂度是O(n^2), 空间复杂度忽略不计

直接插入排序最好情况下能够只比较n - 1次,不用移动

改进的冒泡排序最好情况下, 也只比较n - 1次即可

选择排序比较次数与初始序列无关,都需要比较n^2次, 但是元素移动与初始有关

  • 快速排序、归并排序、堆排序都是比较高效的排序方法, 时间复杂度都在O(nlogn)

快排就是与选择的基准元素有关,归并排序就与初始序列无关

  • 希尔排序介于简单排序和高效排序之间

  • 稳定的算法有:冒泡、直接插入、折半插入、归并

你可能感兴趣的:(学习中的电子笔记,数据结构)