目录
内部排序,外部排序
若待排序记录都在内存中,称为内部排序;
若待排序记录一部分在内存,一部分在外存,则称为外部排序。
注:外部排序时,要将数据分批调入内存来排序,中间结果还要及时放入外存,显然外部排序要复杂得多。
排序算法的稳定性根据相同元素排序前后的顺序是否改变来确定
一、归并排序
递归实现 - > 自上向下
非递归排序 - > 自下向上
时间复杂度:
先分再合
/* 将序列对半拆分直到序列长度为1*/
void MergeSort_UptoDown(int *num, int start, int end)
{
int mid = start + (end - start) / 2;
if (start >= end)
return;
MergeSort_UptoDown(num, start, mid);
MergeSort_UptoDown(num, mid + 1, end);
Merge(num, start, mid, end);
}
void Merge(int *num, int start, int mid, int end)
{
int *temp = (int *)malloc((end-start+1) * sizeof(int)); //申请空间来存放两个有序区归并后的临时区域
int i = start;
int j = mid + 1;
int k = 0;
while (i <= mid && j <= end)
if (num[i] <= num[j])
temp[k++] = num[i++];
else
temp[k++] = num[j++];
while (i <= mid)
temp[k++] = num[i++];
while (j <= end)
temp[k++] = num[j++];
//将临时区域中排序后的元素,整合到原数组中
for (i = 0; i < k; i++)
num[start + i] = temp[i];
free(temp);
}
二、交换排序
1.快速排序
基本思想:
任取一个元素 (如第一个) 为中心
所有比它小的元素一律前放,比它大的元素一律后放,形成左右两个子表;
对各子表重新选择中心元素并依此规则调整,直到每个子表的元素只剩一个
时间复杂度
#include<bits/stdc++.h>
using namespace std;
const int maxn=1000005;
int n,a[maxn];
void qsorts(int l,int r)
{
int mid=a[(l+r)/2];
int i=l,j=r;
do{
while(a[i]<mid)i++;
while(a[j]>mid)j--;
if(i<=j)
{
swap(a[i],a[j]);
i++;
j--;
}
}while(i<=j);
if(j>l)qsorts(l,j);
if(i<r)qsorts(i,r);
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
qsorts(1,n);
for(int i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<endl;
}
2.冒泡排序
void bubble_sort(int a[], int n)
{
int i,j,temp;
for (j=0;j<n-1;j++)
{
for (i=0;i<n-1-j;i++)
{
if(a[i]>a[i+1])
{
temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
}
}
}
}
三、插入排序
每步将一个待排序的对象,按其关键码大小,插入到前面已经排好序的一组对象的适当位置上,直到对象全部插入为止。
即边插入边排序,保证子序列中随时都是排好序的
1.直接插入排序(基于顺序查找)
void insertSort(int* a,int T){
int tmp,p;
for(int i=1;i<T;i++){
tmp=a[i];
p=i-1;
while(p>=0&&tmp<a[p]){
a[p+1]=a[p];
p--;
}
a[p+1]=tmp;
}
}
2.折半插入排序(基于折半查找)
(1)基本思想
折半插入排序的基本思想是:顺序地把待排序的序列中的各个元素按其关键字的大小,通过折半查找插入到已排序的序列的适当位置。
(2)运行过程
直接插入排序的运作如下:
1、将待排序序列的第一个元素看做一个有序序列,把第二个元素到最后一个元素当成是未排序序列。
2、从头到尾依次扫描未排序序列,将扫描到的每个元素插入有序序列的适当位置,在查找元素的适当位置时,采用了折半查找方法。(如果待插入的元素与有序序列中的某个元素相等,则将待插入元素插入到相等元素的后面。)
void binary_insertion_sort(int arr[], int len)
{
int i, j, temp, m, low, high;
for (i = 1; i < len; i++)
{
temp = arr[i];
low = 0; high = i-1;
while (low <= high)
{
m = (low +high) / 2;
if(arr[m] > temp)
high = m-1;
else
low = m+1;
}
}
for (j = i-1; j>=high+1; j--)
arr[j+1] = arr[j];
arr[j+1] = temp;
}
3.希尔排序(基于逐趟缩小增量)
#include <stdio.h>
#include <malloc.h>
void shellSort(int *a, int len)
{
int i, j, k, tmp, gap; // gap 为步长
for (gap = len / 2; gap > 0; gap /= 2) { // 步长初始化为数组长度的一半,每次遍历后步长减半,
for (i = 0; i < gap; ++i) { // 变量 i 为每次分组的第一个元素下标
for (j = i + gap; j < len; j += gap) { //对步长为gap的元素进行直插排序,当gap为1时,就是直插排序
tmp = a[j]; // 备份a[i]的值
k = j - gap; // j初始化为i的前一个元素(与i相差gap长度)
while (k >= 0 && a[k] > tmp) {
a[k + gap] = a[k]; // 将在a[i]前且比tmp的值大的元素向后移动一位
k -= gap;
}
a[k + gap] = tmp;
}
}
}
}
int main(void)
{
int i, len, * a;
printf("请输入要排的数的个数:");
scanf("%d",&len);
a = (int *)malloc(len * sizeof(int)); // 动态定义数组
printf("请输入要排的数:\n");
for (i = 0; i < len; i++) { // 数组值的输入
scanf("%d",&a[i]);
}
shellSort(a, len); // 调用希尔排序函数
printf("希尔升序排列后结果为:\n");
for (i = 0; i < len; i++) { // 排序后的结果的输出
printf("%d\t",a[i]);
}
printf("\n");
return 0;
}
四、选择排序
0.直接选择
选择排序(Selection sort)是一种简单直观的排序算法。它的工作原理是每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
void Selection_Sort(int Arr[])
for (int i = 0; i < BUFFSIZE - 1; i++)
for (int j = i + 1; j < BUFFSIZE; j++)
if (Arr[i] < Arr[j]) // 将大的元素移到前面
{
int tmp = Arr[i];
Arr[i] = Arr[j];
Arr[j] = tmp;
}
//输出排序后的元素
for (int i = 0; i < BUFFSIZE; i++)
cout << Arr[i] << " ";
cout << endl;
}
1.堆排序
一个序列,如果将序列看成一个完全二叉树,非终端结点的值均小于或大于左右子结点的值。
利用树的结构特征来描述堆,所以树只是作为堆的描述工具,堆实际是存放在线形空间中的。
- 首先堆是一颗完全二叉树
- 其次堆中存储的值是偏序
Min-heap(小根堆): 父节点的值小于或等于子节点的值
Max-heap(大根堆): 父节点的值大于或等于子节点的值
基本思路
将无序序列建成一个堆
输出堆顶的最小(大)值
使剩余的n-1个元素又调整成一个堆,则可得到n个元素的次小值
重复执行,得到一个有序序列
堆的重新调整
输出堆顶元素后,以堆中最后一个元素替代之
将根结点与左、右子树根结点比较,并与小者交换
重复直至叶子结点,得到新的堆
时间效率:
空间效率:
稳 定 性:不稳定
适用于 较大的情况
代码:
int heap[N],sz=0;
void push(int x)
{
int i=sz++;
while(i>0)//往上走
{
//父结点的编号
int p=(i-1)/2;
//如果不需要再交换就break;
if(heap[p]<=x)break;
heap[i]=head[p];
i=p;
}
heap[i]=x;
}
//删除最小值:先把最小值丢掉,先把最后一个节点的值放到根节点处,然后排序交换即可
int pop()
{
//最小值
int ret=heap[0];
int x=heap[--sz];
int i=0;
while(i*2+1<sz)//因为堆是完全二叉树偏左嘛
{
//左右儿子
int a=i*2+1,b=i*2+2;
//选出儿子中最小的
if(b<sz&&heap[b]<heap[a])a=b;
//如果不需要交换就break
if(heap[a]>=x)break;
//交换
heap[i]=heap[a];
i=a;
}
heap[i]=x;
return ret;//返回被丢掉的那个最小值
}
2.二叉堆
二叉堆是一种支持插入、删除、查询最值的数据结构,是一棵满足堆性质的完全二叉树,树上的每一个节点都带有一个权值。
大根堆:
树上任意一个节点的权值都小于等于其父节点的权值。
小根堆:
树上任意一个节点的权值都大于等于其父节点的权值。
二叉堆的储存可以采用层次序列的储存方式,直接用一个数组保存:按从左到右,从上到下的顺序依次为二叉堆上的节点编号,如果根节点的编号为1的话,每个节点的左子节点的编号为根节点编号,右子节点的编号为根结点编号,每个节点的根节点的编号为自身编号 / 2。
以大根堆为例讨论二叉堆的常见操作:
二叉堆的插入操作:
将新插入的值放在储存二叉堆的数组的末尾,然后按照二叉堆的规则向上交换,直到满足二叉堆的性质,时间复杂度为二叉堆的深度,即:。
返回堆顶值:
大根堆堆顶的值为堆中的最大值,小根堆堆顶的值为堆中的最小值。
移除堆顶的值:
首先,将堆顶的值与数组末尾的节点交换,之后移除数组末尾的节点(在下面的样例中,移除节点通过记录节点个数的n−1n-1n−1来实现);然后,将新的堆顶的值通过交换的方式向下调整,直至满足二叉堆的性质。
删除任意一个元素:
与删除对顶元素类似,将要删除的元素与数组末尾的元素交换,时候数组长度-1,然后分别检查是否需要向上或者向下调整,时间复杂度为。
二叉树的实现可以手写,也可以使用STL
3.手写二叉堆代码
int heap[MAX], n;
void up(int pos) // 向上调整
{
while (pos > 1)
{
if (heap[pos] > heap[pos / 2])
{
swap(heap[pos], heap[pos / 2]);
pos /= 2;
}
else
break;
}
}
void insert(int val) // 插入节点
{
heap[++n] = val;
up(n);
}
int top() // 返回堆顶元素
{
return heap[1];
}
void down(int pos) // 向下调整
{
int son = pos * 2;
while (son <= n)
{
if (son < n && heap[son] <= heap[son + 1])
son++; // 最大的子节点
if (heap[pos] < heap[son])
{
swap(heap[pos], heap[son]);
pos = son;
son = pos * 2;
}
else
break;
}
}
void pop() // 弹出堆顶元素
{
heap[1] = heap[n];
n--;
down(1);
}
void remove(int pos) // 删除指定位置的元素
{
heap[pos] = heap[n];
n--;
up(pos);
down(pos);
}