p.s.自用
p.ss.基于C语言
目录
*相关概念*
就地排序:在排序的过程中,只使用到了存储数据的空间,没有使用其他额外空间
非接地排序:在排序的过程中,使用了其他额外空间
->空间复杂度
内部排序:待排序的数据能一次性放到内存中
外部排序:待排序的数据不能一次性放到内存中
稳定排序:排序前后,相同数据的相对位置没有发生变化
不稳定排序:排序前后,相同数据的相对位置发生了变化
p.s.所有排序默认从1位置开始
基于插入
直接插入排序算法
思路:在添加新的数据时,使用顺序查找的方式找到该数据要插入的位置
时间复杂度:O(n^2)
类别:就地排序 & 内部排序 & 稳定排序
//直接插入排序
for (int i = 2; i <= n; i++)//由于第一个数据不用排序,可直接从第二个数据开始
{
int t = a[i];//保存待排序的数据
int ind = i;//下标
//有序区:1 到 i-1
for (int j = 1; j < i; j++)
{
if (a[j] > t)
{
ind = j;
break;
}
}
for (int j = i; j > ind; j--)
a[j] = a[j - 1];
a[ind] = t;
}
//直接插入排序-改良版
for (int i = 2; i <= n; i++)//由于第一个数据不用排序,可直接从第二个数据开始
{
int t = a[i];//保存待排序的数据
int j;
for (j = i; j > 0; j--)
{
if (t < a[j - 1])
a[j] = a[j - 1];
else
break;
}
a[j] = t;
}
优化方案: 折半插入排序、2-路插入排序
希尔(shell)排序算法 / 缩小增量排序
思路:在直接插入排序算法的基础上,对待排序的数据进行分组,先对每组进行排序,然后不断缩小组数,不断排序,最终缩小为1组
时间复杂度:与增量序列的选择有关,最坏时间复杂度是O(n^2)
类别:就地排序 & 内部排序 & 不稳定排序
//希尔插入排序
int k = 0;
for (int d = n / 2; d >= 1; d = d / 2)//分组数
{
k++;//计算趟数
for (int i = 1 + d; i <= n; i++)//以增加d分组,对每组进行直接插入排序
{
int t = a[i];//保存待排序的数据
int j;
for (j = i - d; j >= 1; j = j - d)
{
if (t < a[j])
a[j + d] = a[j];
else
break;
}
a[j + d] = t;
}
}
基于交换
交换:根据数据之间的比较,把每个数据都放在应在的位置
冒泡排序算法
思路:通过不断比较两个相邻元素,若这两个元素乱序,则交换位置,从而实现每趟都把最大的数据交换到最后
时间复杂度:O(n^2)
类别:就地排序 & 内部排序 & 稳定排序
//冒泡排序
for (int i = 1; i <= n - 1; i++)//循环次数
{
for (int j = 1; j <= n - i; j++)
{
if (a[j] > a[j + 1])
{
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
//冒泡排序-优化
for (int i = 1; i <= n - 1; i++)//循环次数
{
int flag = 0;
for (int j = 1; j <= n - i; j++)
{
if (a[j] > a[j + 1])
{
flag = 1;
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
if (!flag)//表示已排序完成
break;
}
快速排序
思路:基于递归
首先选定一个基准数x作为比较的标准,把比x小的数据放在x前面,比x大的数据放在x后面;之后x把序列分成了两部分,这两部分都是乱序的,再分别快排;
基准数的选择:第一个数、最后一个数、中间位置的数
时间复杂度:O(nlogn)
类别:就地排序 & 内部排序 & 不稳定排序
//快速排序-以数组第一个数为基准数
void Quick_sort(int a[], int l, int r)
{
if (l < r)
{
int i, j, x;
i = l; j = r;
x = a[l];
while (i < j)
{
while (i < j && a[j] > x)
j--;
if (i < j)
{
a[i] = a[j];
i++;
}
while (i < j && a[i] < x)
i++;
if (i < j)
{
a[j] = a[i];
j--;
}
}
a[i] = x;
Quick_sort(a, l, i - 1);
Quick_sort(a, i + 1, r);
}
}
基于选择
选择:每次选出一个合法的数据放在其应在的位置
简单选择排序算法
思路:每趟从待排序区中选择一个最小的数,放到待排序区的第一个位置,从而实现升序排列。
时间复杂度:O(n^2)
类别:就地排序 & 内部排序 & 不稳定排序
//简单选择排序
for (int i = 1; i < n; i++)
{
int minn = i;
for (int j = i + 1; j <= n; j++)
{
if (a[j] < a[minn])
minn = j;
}
int t = a[minn];
a[minn] = a[i];
a[i] = t;
}
堆排序算法
时间复杂度:O(nlogn)
类别:就地排序 & 内部排序 & 不稳定排序
思路:
1.建堆:用一个数组保存
1)自我初始化:在原数组的基础上进行调整
从子树入手,由小及大调整每颗子树。
对于每颗子树,向下调整;让根节点和其左右孩子比较,最小值和根节点交换,继续向下调整子树
void downAdjust(int a[], int i, int n)
{
int parent = i;
int child = 2 * i;
while (parent * 2 <= n)
{
child = 2 * parent;
if (child + 1 <= n && a[child + 1] < a[child])//查找最小的孩子
child = child + 1;
if (a[parent] < a[child])//父亲比最小孩子小
break;
else
{
int t = a[parent];
a[parent] = a[child];
a[child] = t;
parent = child;
}
}
}
2)通过插入建堆
每插入一个数据,就调整一次。新插入的数据放在最后,若比父亲大/是根节点就不调整,否则就向上调整
void upAdjust(int a[], int i)
{
int child = i;
int parent = child / 2;
while (child > 1)
{
parent = child / 2;
if (a[parent] <= a[child])
break;
else
{
int t = a[parent];
a[parent] = a[child];
a[child] = t;
child = parent;
}
}
}
2.循环n次,每次输出最小的数
输出最小的数 根节点a[1]
删掉a[1] -> 让堆中最后一个节点替换a[1],然后重新对a[1]向下调整
//1.1)自我初始化的参考主函数
int main()
{
int a[105] = { 0 };
int n;//待排序的数据个数
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
//堆排列
//1.建堆
//1.1)自我初始化
for (int i = n / 2; i >= 1; i--)//枚举
downAdjust(a, i, n);
//2.循环n次,每次输出最小的数
int dsize = n;
for (int i = 1; i <= n; i++)
{
printf("%d ", a[1]);
a[1] = a[dsize--];
downAdjust(a, 1, dsize);//调整
}
}
//1.2)插入建堆的参考主函数
int main()
{
int a[105] = { 0 };
int n;//待排序的数据个数
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
//1.2)插入建堆
upAdjust(a, i);
}
//2.循环n次,每次输出最小的数
int dsize = n;
for (int i = 1; i <= n; i++)
{
printf("%d ", a[1]);
a[1] = a[dsize--];
downAdjust(a, 1, dsize);//调整
}
}
堆的应用
1. 优先队列 -> 堆
2.输入一亿个数,选出前100个最大的输出?要考虑空间和时间复杂度 -> 小顶堆
先读入100个数,建立小顶堆
然后,当读入一个数x,
若x<=a[1],直接读入下一个数
若x>a[1],a[1]=x,向下调整为小顶堆
最后,小顶堆中的数据就是答案
其他
归并排序【外部排序】
思路:先分到最小,再排序合并
时间复杂度:O(nlogn)
类别:非就地排序 & 外部排序 & 稳定排序
//二路归并
void merge(int a[], int l, int mid, int r)//在合并过程中把顺序排好
{
int i = l;
int j = mid + 1;
int t[105] = { 0 };
int k = 0;//t数组的下标
while (i <= mid && j <= r)
{
if (a[i] <= a[j])
{
t[k] = a[i];
k++;
i++;
}
else
{
t[k] = a[j];
k++;
j++;
}
}
while (i <= mid)
{
t[k] = a[i];
k++;
i++;
}
while (j <= r)
{
t[k] = a[j];
k++;
j++;
}
for (int g = 0; g < k; g++)
a[l + g] = t[g];
}
void mergeSort(int a[], int l, int r)//把数组a的[l,r]从中间位置分为两部分
{
if (l < r)
{
int mid = (l + r) / 2;
//分割l-mid
mergeSort(a, l, mid);
//分割mid+1-r
mergeSort(a, mid + 1, r);
//合
merge(a, l, mid, r);
}
}
int main()
{
int a[105] = { 0 };
int n;//待排序的数据个数
scanf("%d", &n);
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
//归并排序
mergeSort(a, 1, n);
//输出
for (int i = 1; i <= n; i++)
printf("%d ", a[i]);
}
基于计数的排序
计数排序(以空间换时间)
统计一下每个数出现的次数,然后直接按次数输出即可
类别:非就地排序 & 内部排序 & 不稳定排序
缺点:
a.无法对负整数/小数进行排序;【可优化】
b.及其浪费空间;
c.是一个不稳定排序;【可优化】
桶排序(以空间换时间)
将各数据根据规则分别放入桶内,再在各个桶内进行排序
基数排序
从数据的最低有效位到最高有效位 逐位 比较(先补齐位数:在较小数据前加0)
类别:非就地排序 & 内部排序 & 稳定排序