第十章 排序
10.2 插入排序
直接插入排序
找到插入的位置,后边的往后移
二分插入法
查找位置的方式用二分
表插入法
思想是不动元素的位置,给每个元素配一个link指向下一个元素的序号,插入时,从第一个开始遍历,直到找到一个比自己大的,假设当前元素是insert,当前比自己大的是current,当前比自己大的前一个是pre,那么我们把pre的link填成我们的insert的序号,insert的link填成current。
Shell插入排序
假设有n个元素,每间隔b个取一个元素,组成一组,组内排序,不断减小b到1.
如 排序 0 1 2 3 4 5
d = 3
0 3 一组排序 1 4 一组排序 2 一组排序
d = 2
0 2 4 一组排序 1 3 5 一组排序
d = 1
0 1 2 3 4 5一组排序
其实感觉优化力度不大,没啥用
10.3 选择排序
直接选择排序
每次选出最小的值,放在最前面
树形选择排序
就是把元素放到一棵二叉树的叶子节点,每两个节点选出一个小值,给上面的节点,一直到根节点,这样根节点就是当前的最小值,然后递归找到这个最小值,把这个最小值改成无穷大,一直更新回去到根节点,就能再得到一个最小值,不断重复即可,可知每得到一个最小值,需要lgn的复杂度,整体时间复杂度是nlgn
堆排序
就是把一个数组当成一个二叉树,调整为大顶堆,让后把最大值放到最后,对剩下的元素继续建立大顶堆,直到排序完成。
建立大顶堆的好处是,容易访问左右孩子。
在第一次构建大顶堆的时候,需要调整所有非叶子节点(看看是否有比自己大的叶子节点),从最后一个非叶子节点调整,一直到根节点。
一般是用数组来进行堆排序,把数组当作一个丰满二叉树,并且这个数最后一次的叶子节点是从左到右一次排列着的。
即两种情况:
假如树有n个节点,如果是满二叉树即第一种情况
可知,最后一层有(n+1)/2个节点,前面有n/2个节点。那么最后一个非叶子节点在数组种的序号即 n/2-1
假如最后一层有m个节点,那么可知,去掉最后一层的m个节点有n-m个节点,对应了(n-m)/2个非叶子节点,而最后一层的m个节点又带来了m/2个节点,所以总共的非叶子节点个数是 (n-m)/2+m/2=n/2个,那么最后一个非叶子节点在数组中的序号还是n/2-1
所以我们只需要倒着遍历这些非叶子节点,把大的送到根即可,如果换下来的节点比当前的叶子节点还小,也需要递归更新。
for(i=n/2;i>=0;i--) { //比左右孩子节点是否有比自己大的 // 如果右孩子不越界,并且右孩子大于左孩子 // 用右孩子替换自己,并且递归调整右孩子为大顶堆 // 否则 // 用左孩子替换自己,并且递归调整左孩子为大顶堆 }
代码:
// BigHeap.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <stdio.h>
#include <vector>
using namespace std;
void BuildBigHeap(vector<int>& v, int root, int len)
{
if (root >= len)return;
// 数组如果是从1开始的话,那么左孩子是root*2,右孩子是root*2+1
// 但这里数组是从0开始的,所以在原来的基础上加1
int left = root * 2+1, right = root*2+2;
//比左右孩子节点是否有比自己大的
// 如果右孩子不越界,并且右孩子大于左孩子
// 用右孩子替换自己,并且递归调整右孩子为大顶堆
// 否则
// 用左孩子替换自己,并且递归调整左孩子为大顶堆
if ((left<len && v[left]>v[root]) || (right<len && v[right]>v[root]))
{
if (right<len && v[left]<v[right])
{
swap(v[root], v[right]);
BuildBigHeap(v, right, len);
}
else
{
swap(v[root], v[left]);
BuildBigHeap(v, left, len);
}
}
}
void BigHeapSort(vector<int>& v)
{
for (int i = v.size() / 2-1; i >= 0; i--)
{
BuildBigHeap(v, i, v.size());
}
swap(v[0], v[v.size() - 1]);
for (int i = 1; i < v.size(); i++)
{
BuildBigHeap(v, 0, v.size() - i);
swap(v[0], v[v.size() - i-1]);
}
}
int main()
{
vector<int> v;
v.push_back(1);
v.push_back(3);
v.push_back(6);
v.push_back(2);
v.push_back(4);
v.push_back(7);
v.push_back(5);
BigHeapSort(v);
for (int i = 0; i != v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
return 0;
}
10.4 交换排序
冒泡排序
不断从左到右遍历,发现后边的比自己小,就把自己与其交换,直到一轮遍历,一次交换也没有发生
快速排序
假设要排序n个数,取第一个位置的值作为中间值,比这个值大的放右边,比这个值小的放左边。
用两个指针,l和r,先l=0,r=n-1。
先不断将r向左移动,
直到碰到一个比中间值小的值,然后交换l和r的值,swap(v[l], v[r]),这样保证给l一个小于关键值的值,
或着没找到比关键字小的值,此时l==r,可知当前最左边就是最小值了,排序右边即可,
再不断将l向右移动直到碰到一个值大于中间值,把这个大于中间值的数换给r,swap(v[l], v[r]),这样保证换给r一个大于中间值的数值,由于先走r,保证了l的位置不可能是最大值,除非所有数相等,所以l向右走一定能找到比自己大的值,如果l向右走的过程中碰到r,说明r此时的数是全局最大的数,保证了左边的数都小于这个数。
当l==r时,该位置左边全是比中间值小的值,右边全是比中间大的值,这个保证是关键。然后再快排左边,再快排右边。最终就完成了排序。
注意一定得先走r,再走l,不然就破坏了正确性。
正确性:
假设l==r时,r右边还有比当前值小的。 如果是r向左走碰到了l,说明r没找到比关键字更小的值,即l==r的这个位置的右边全是大于等于的数。
如果是l向右走碰到了r,因为我们是先走的r,一定保证给l一个小于关键字的值,所以,说明l这一路走过来没有找到更大的值,所以保证了l==r的这个地方的左边全是小于等于的数。
代码:
// QuickSort.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
using namespace std;
void QuickSort(vector<int> &v, int s, int e)
{
if (s >= e)return;
int l = s, r = e, key=v[s];
while (l < r)
{
while (l < r && v[r] >= key)r--;
swap(v[l], v[r]);
while (l < r && v[l] <= key)l++;
swap(v[l], v[r]);
}
QuickSort(v, s, l-1);
QuickSort(v, r+1, e);
}
int main()
{
vector<int> v;
v.push_back(5);
v.push_back(4);
v.push_back(3);
v.push_back(2);
v.push_back(1);
QuickSort(v, 0, v.size()-1);
for (int i = 0; i != v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
return 0;
}
归并排序
不断的把数组二分成两个数组,分别排好两个数组后,然后合并两个数组到原数组,可知需要占用额外的空间。
// MergeSort.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include <vector>
using namespace std;
void MergeSort(vector<int>& v, int s, int e)
{
if (s >= e)return;
int mid = (s + e)/2;
MergeSort(v, s, mid);
MergeSort(v, mid+1, e);
vector<int> left, right;
for (int i = s; i <= mid; i++)
left.push_back(v[i]);
for (int i = mid + 1; i <= e; i++)
right.push_back(v[i]);
int i = 0, j = 0;
while (i < left.size() || j < right.size())
{
if (i < left.size() && j < right.size())
{
if (left[i] < right[j])
{
v[s + i + j] = left[i];
i++;
}
else
{
v[s + i + j] = right[j];
j++;
}
}
else
{
if (i < left.size())
{
v[s + i + j] = left[i];
i++;
}
if (j < right.size())
{
v[s + i + j] = right[j];
j++;
}
}
}
}
int main()
{
vector<int> v;
v.push_back(5);
v.push_back(4);
v.push_back(3);
v.push_back(2);
v.push_back(1);
MergeSort(v, 0, v.size()-1);
for (int i = 0; i != v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
return 0;
}
10.6 基数排序
基数排序又称分配排序,应对排序的规则多的情况
多排序码的排序
元素比较大小要看多种规则,比如扑克牌,数组大于花色,数组相同的时候比较花色
所以可以先安装花色将牌分开,然后排序,然后再合并到一起。
静态链式基数排序
根扑克牌一样,先根据优先级高的规则排序,然后根据优先级低的规则排序。