下面介绍几种排序方法
所用结构体如下:
template<class KEY, class OTHER>
struct SET {
KEY key;//关键字
OTHER other;//包含姓名、性别、出生日期等信息
};
首先将由第一个数据元素组成的序列看成是有序的,然后将剩余的n-1个元素依次插入到前面的已排好序的子序列中去,使得每次插入后的子序列也是有序的。
template<class KEY,class OTHER>
void simpleinsertsort(SET<KEY, OTHER>a[], int size) {
int k;
SET<KEY, OTHER> tmp;
for (int j = 1; j < size; ++j) {
tmp = a[j];
for (k = j - 1; tmp.key < a[k].key && k >= 0; --k)
a[k + 1] = a[k];
a[k + 1] = tmp;
}
}
利用二分查找法,快速地找到 a [ j ] a[j] a[j]的插入位置。从而达到减少比较次数的目的 ,移动次数不变,故最坏情况下总的比较次数还是 O ( N 2 ) O(N^2) O(N2)
希尔排序是插入排序算法的改进,直接插入排序的高代价主要由大量的移动产生 ,希尔的想法是避免大量的数据移动,先是比较那些离得稍远些的元素,这样,一次交换就相当于直接插入排序中的多次交换。然后比较那些离得近一点的元素,以此类推,逐步逼近直接的插入排序 。
template <class KEY, class OTHER>
void shellSort(SET<KEY, OTHER> a[], int size)
{
int step, i, j;
SET<KEY, OTHER> tmp;
for (step = size/2; step > 0; step /= 2) //step为希尔增量
for (i = step; i < size; ++i) {
tmp = a[i];
for (j = i - step; j >= 0 && a[j].key > tmp.key; j -= step)
a[j+step] = a[j];
a[j+step] = tmp;
}
}
首先,从n个元素中选出关键字最小的元素。再从剩下的(n-1)个元素中选出关键字最小的元素,依次类推,每次从剩下的元素序列中挑出关键字最小的元素,直至序列中最后只剩下一个元素为止。这样,把每次得到的元素排成一个序列,就得到了按非递减序排列的排序序列。
//直接选择排序
template <class KEY, class OTHER>
void simpleSelectSort(SET<KEY, OTHER> a[], int size)
{
int i, j, min;
SET<KEY, OTHER> tmp;
for (i = 0; i < size - 1; ++i) {
min = i;
for (j = i + 1; j < size; ++j)
if (a[j].key < a[min].key) min = j;//找到最小值
//最小值与第i位的交换
tmp = a[i];
a[i] = a[min];
a[min] = tmp;
}
}
【注:关于优先级队列的内容见:数据结构笔记六】
一个例子
例如,要排序39, 36, 58, 23, 44, 97, 31, 14, 26和77,先将这些元素创建一个堆
执行一次出队操作后:
像这样不断进行,既可以得到由小到大排列的数组。
代码实现
堆排序和优先级队列中的堆有三个细小的区别:
//堆排序
//首先进行堆的构造
template<class KEY, class OTHER>
void heapsort(SET<KEY, OTHER> a[], int size) {
int i;
SET<KEY, OTHER> tmp;
//创建初始的最大化堆
for (i = size / 2 - 1; i >= 0; i--) {
percolatedown(a, i, size);//从第一个非叶节点开始进行调整
}
//执行n-1次出队
for (i = size - 1; i > 0; --i) {
//将a[0]和a[i]交换位置
tmp = a[0];
a[0] = a[i];
a[i] = tmp;
percolatedown(a, 0, i);//此时需要往下过滤的元素个数只有i个,后面是已经排好序的
}
}
//percolatedown函数的实现
//此函数共有三个参数:原数组,调整的元素,整个需要调整的树的大小
template <class KEY, class OTHER>
void percolateDown(SET<KEY, OTHER> a[], int hole, int size)
{
int child;
SET<KEY, OTHER> tmp = a[hole];
for (; hole * 2 + 1 < size; hole = child) {//hole*2+1为hole的左孩子的下标
child = hole * 2 + 1;
if (child != size - 1 && a[child + 1].key > a[child].key)
child++;//child为节点值最大的子节点
if (a[child].key > tmp.key) a[hole] = a[child];
else break;
}
a[hole] = tmp;
}
//冒泡排序
template<class KEY, class OTHER>
void bubblesort(SET<KEY, OTHER>a[], int size) {
SET<KEY, OTHER> tmp;
bool flag = true;//记录一次起泡过程中是否发生交换
for (int i = 1; i < size; ++i) {//最多起泡size-1次
flag = false;
for (int j = 0; j < size - i; ++j) {
if (a[j + 1].key < a[j].key) {
tmp = a[j];
a[j] = a[j+1];
a[j + 1] = tmp;
flag = true;
}
if (!flag) break;//如果在一次起泡中并未发生数据的交换说明已经排好了序
}
}
}
在待排序的序列中选择一个数据元素,以该元素为标准,将所有数据元素分为两组,第一组的元素均小于或等于标准元素,第二组的数据元素均大于标准元素。第一组的元素放在数组的前面部分,第二组的数据元素放在数组的后面部分,标准元素放在中间。这个位置就是标准元素的最终位置。这称为一趟划分。然后对分成的两组数据重复上述过程,直到所有的元素都在适当的位置为止。(注意其中的递归思想)
如何来找到中心点?
如何对数据进行划分?
//快速排序
//划分函数的实现
//先分堆后排序
template <class KEY, class OTHER>
int divide(SET<KEY, OTHER> a[], int low, int high)
{
SET<KEY, OTHER> k = a[low];
do {
while (low < high && a[high].key >= k.key) --high;
if (low < high) { a[low] = a[high]; ++low; }
while (low < high && a[low].key <= k.key) ++low;
if (low < high) { a[high] = a[low]; --high; }
} while (low != high);
a[low] = k;
return low;//返回划分点所在位置
}
//快速排序的实现
//一次划分,两次递归
template <class KEY, class OTHER>
void quickSort(SET<KEY, OTHER> a[], int low, int high)
{
int mid;
if (low >= high) return;
mid = divide(a, low, high);//即划分所返回的数值
quickSort(a, low, mid - 1);//排序左一半
quickSort(a, mid + 1, high);//排序右一半
}
//快速排序的包裹函数
//为了和其它的排序算法参数一致
//因为采用了递归算法,所以在参数上会多加了两个控制其起始位置的参数
template <class KEY, class OTHER>
void quickSort(SET<KEY, OTHER> a[], int size)
{
quickSort(a, 0, size - 1);
}
时间复杂度分析
T ( N ) = T ( i ) + T ( N − i − 1 ) + c N ( N 表 示 进 行 了 N 次 比 较 ) T ( 0 ) = T ( 1 ) = 1 \begin{array}{ll} T(N) = T(i) + T(N-i-1) + cN (N表示进行了N次比较)\\ T(0) = T(1) = 1 \end{array} T(N)=T(i)+T(N−i−1)+cN(N表示进行了N次比较)T(0)=T(1)=1
由此公式可得:
基本概念
——合并两个已排序的有序表
顺序比较两者的相应元素,小者移入另一表中,反复如此,直至其中一表为空为止,将另一表中剩余结点自左至右复制到表C的剩余位置。
一个例子
代码实现:
//归并排序
//归并
template <class KEY, class OTHER>
void merge(SET<KEY, OTHER> a[], int left, int mid, int right)
{
SET<KEY, OTHER>* tmp = new SET<KEY, OTHER>[right - left + 1];
int i = left, j = mid, k = 0;
while (i < mid && j <= right) //两表都未结束
if (a[i].key < a[j].key) tmp[k++] = a[i++];
else tmp[k++] = a[j++];
while (i < mid) tmp[k++] = a[i++]; //前半部分没有结束
while (j <= right) tmp[k++] = a[j++]; //后半部分没有结束
for (i = 0, k = left; k <= right; ) a[k++] = tmp[i++];//再把这个腾回去
delete[] tmp;
}
//归并排序法
//递归思想,n=1为截止条件,否则对前一半和后一半分别调用归并排序,并再次归并两个已排序的数组
template <class KEY, class OTHER>
void mergeSort(SET<KEY, OTHER> a[], int left, int right)
{
int mid = (left + right) / 2;
if (left == right) return;
mergeSort(a, left, mid);
mergeSort(a, mid + 1, right);
merge(a, left, mid + 1, right);
}
//归并排序的包裹函数
//为了和其它的排序算法参数一致
//因为采用了递归算法,所以在参数上会多加了两个控制其起始位置的参数
template <class KEY, class OTHER>
void mergeSort(SET<KEY, OTHER> a[], int size)
{
mergeSort(a, 0, size - 1);
}
时间复杂度分析
T ( 1 ) = 1 T ( N ) = 2 T ( N / 2 ) + N \begin{array}{ll} T(1) = 1\\ T(N) = 2T(N/2) + N \end{array} T(1)=1T(N)=2T(N/2)+N
同上分析得时间复杂度为 O ( N l o g N ) O(NlogN) O(NlogN)
(唔,貌似我们不要求掌握)
基本概念
(wu 要考试了,本来说跟着课程进度更新这一个系列,貌似时间不太够啦,可能要拖到寒假了bye~)