定义:全部记录都可以同时调入内存进行的排序
定义: 设有记录序列:{ R1、R2 ……….. Rn }
其相应的关键字序列为: { K1、K2 ……….. Kn };
若存在一种确定的关系: Kx <= Ky <= … <= Kz则将记录序列 { R1、R2 ……….. Rn }
排成按该关键字有序的序列 { Rx、Ry ……….. Rz } 的操作,称之为排序。
注意:关系是任意的,如通常使用的小于、大于等关系。
直接插入排序
例:36、24、10、6、12存放在 r 数组的下标为 1 至 5 的元素之中,用直接插入法将其排序。结果仍保存在下标为 1 至 5 的元素之中。
r[0] 用作哨兵。共执行 5 遍操作。
每遍操作:
- 先将元素复制内容放入num[0],
- 再将本元素同已排序的序列,从尾开始进行比较。在已排序的序列中寻找自己的位置,进行插入
- 或者寻找不到,则一直进行到哨兵为止。意味着本元素最小,应该放在 num[1] 。
每一遍,排序的序列将增加一个元素。如果序列中有 n 个元素,那么最多进行n 遍即可。
void insert_sort(int num[],int len)
{
for (int i = 2; i < len; i++) {
//遍历数组排序
num[0] = num[i];
int j = i;
while (j--) {
//在前i个有序数组找插入位置
if (num[0] < num[j]) {
//第i个元素小于第j个元素,将第j个元素向后移
num[j + 1] = num[j];
}
else if (j == 0) {
//若j==0,则第i个元素在前i个元素中最小
num[1] = num[0];
}
else {
num[j + 1] = num[0];//第i个元素大于第j个元素,则第j+1个元素等于第i个元素
break;
}
}
}
for (int i = 1; i < len; i++) {
printf("%d ", num[i]);
}
}
时间复杂度为:T(n)=O(n)
辅助空间:S(n)=O(1)
折半插入排序
void bininsert_sort(int num[], int len)
{
for (int i = 2; i < len; i++) {
num[0] = num[i]; //更新标志
int low = 1, hight = i - 1,mid; //初始化头尾指针
while (low <= hight) {
//折半寻找合适位置
mid = (hight + low) / 2;
if (num[0] < num[mid]) {
hight = mid - 1;
}
else {
low = mid + 1;
}
}
for (int j = i - 1; j >= low; j--) {
//循环后移元素,为插入准备
num[j + 1] = num[j];
}
num[low] = num[0]; //插入
}
for (int i = 1; i < len; i++) {
printf("%d ", num[i]);
}
}
时间复杂度:T(n)=O(n²)
辅助空间:S(n)=O(1)
希尔排序
先取一个正整数d1
然后取d2
直至di=1,即所有记录放进一个组中排序为止。
希尔排序实际是步长递减的多次直接插入排序过程。
特点:
子序列的构成不是简单的“逐段分割”,而是将相隔某个增量的记录组成一个子序列。希尔排序可提高排序速度,因为分组后n值减小,n²更小,而T(n)=O(n²),所以T(n)从总体上看是减小了,关键字较小的记录跳跃式前移,在进行最后一趟增量为1的插入排序时,序列已基本有序。辅助空间:S(n)=O(1)
void shell_sort(int num[], int len)
{
int i, j;
for (i = len/2; i >= 1; i /=2) {
//循环n趟
for (j = 1 + i; j < len; j++) {
//循环i次
num[0] = num[j];
int k = j - i;
while (k > 0 && num[0] < num[k]) {
//每个小组内比较移动
num[k + i] = num[k];
k -= i;
}
num[k + i] = num[0]; //插入位置要比k大i
}
}
for (int i = 1; i < len; i++) {
printf("%d ", num[i]);
}
}
增量序列取法:
最后一个增量值必须为1;
冒泡排序
将第一个记录的关键字与第二个记录的关键字进行比较,若为逆序r[1].key>r[2].key,则交换;然后比较第二个记录与第三个记录;依次类推,直至第n-1个记录和第n个记录比较为止——第一趟冒泡排序,结果关键字最大的记录被安置在最后一个记录上
对前n-1个记录进行第二趟冒泡排序,结果使关键字次大的记录被安置在第n-1个记录位置
重复上述过程,直到“在一趟排序过程中没有进行过交换记录的操作”为止
void bubble_sort(int num[], int len)
{
bool flag = 1; //交换标志
int i = len;
while (i>2&&flag) {
flag = 0;
for (int j = 1; j + 1< i; j++) {
if (num[j] > num[j + 1]) {
int tem = num[j];
num[j] = num[j + 1];
num[j + 1] = tem;
flag = 1;
}
}
i--;
}
for (int i = 1; i < len; i++) {
printf("%d ", num[i]);
}
}
时间复杂度T(n)=O(n²)
辅助空间:S(n)=O(1)
快速排序
通过一趟排序,将待排序记录分割成独立的两部分,其中一部分记录的关键字均比另一部分记录的关键字小,则可分别对这两部分记录进行排序,以达到整个序列有序
排序过程:
- 对num[s……t]中记录进行一趟快速排序,附设两个指针i和j,初始时令i=s,j=t,x=num[0]
- 首先从j所指位置向前搜索第一个关键字小于x的记录,并和x交换
- 再从i所指位置起向后搜索,找到第一个关键字大于x的记录,和x交换
- 重复上述两步,直至i==j为止
- 再分别对两个子序列进行快速排序,直到每个子序列只含有一个记录为止
void quick_sort(int num[], int low, int hight)
{
int i = low, j = hight, x = num[i];
if (low >= hight)
return;
while (i < j) {
//i>j说明排序完成
while (i < j&&num[j] >= x) //i指向标志元素,移动j找位置
j--;
if (i < j) {
num[i] = num[j]; //使i指向的元素等于j指向的元素指向的元素
i++;
}
//接下来移动i指针
while (i < j&&num[i] <= x)
i++;
if (i < j) {
num[j] = num[i];
j--;
}
}
num[i] = x; //找到放置标志元素位置,最后
k_sort(num, low, j - 1); //递归排序x位置左边的元素
k_sort(num, j + 1, hight);//递归排序x位置右边的元素
}
最好情况(每次总是选到中间值作枢轴) T(n)=O(nlog2n)
最坏情况(每次总是选到最小或最大元素作枢轴)T(n)=O(n²)
辅助空间:需栈空间以实现递归
最坏情况:S(n)=O(n)
一般情况:S(n)=O(log2n)
选择排序
排序过程:
void smp_sort(int num[], int len)
{
for (int i = 1; i < len ; i++) {
int min = i;
for (int j = i + 1; j < len; j++) {
if (num[j] < num[min]) {
min = j;
}
}
if (i != min) {
int tem = num[i];
num[i] = num[min];
num[min] = tem;
}
}
for (int i = 1; i < len; i++) {
printf("%d ", num[i]);
}
}
时间复杂度:T(n)=O(n²)
辅助空间:S(n)=O(1)
堆排序
排序过程:
将无序序列建成一个堆,得到关键字最小(或最大)的记录;输出堆顶的最小(大)值后,使剩余的n-1个元素重又建成一个堆,则可得到n个元素的次小值;重复执行,得到一个有序序列
大顶堆:满足Key[i]>=Key[2i+1]&&key>=key[2i+2]称为大顶堆,简单来说就是双亲节点的值总是大于孩子结点,而兄弟节点之间没有约束,左孩子可以大于右孩子,也可以小于右孩子。
小顶堆:满足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]称为小顶堆,双亲节点的值总是小于孩子结点。
特点: 可将堆序列看成完全二叉树,则堆顶元素(完全二叉树的根)必为序列中n个元素的最小值或最大值。
堆排序要解决的问题:
第二个问题解决方法——筛选
输出堆顶元素之后,以堆中最后一个元素替代之;然后将根结点值与左、右子树的根结点值进行比较,并与其中小者进行交换;重复上述操作,直至叶子结点,将得到新的堆,称这个从堆顶至叶子的调整过程为“筛选”。
void buildd_1(int num[], int len) //从根节点向下建小顶堆
{
for (int i = 1; i < len; i++) {
int j = i;
while (j != 0 && num[j] < num[(j - 1) / 2]) {
//孩子节点逐一与双亲节点比较
int tem = num[j];
num[j] = num[(j - 1) / 2];
num[(j - 1) / 2] = tem;
j = (j - 1) / 2;
}
}
}
void buildd_2(int num[],int i, int len) //自底向上建小顶堆
{
int child = 2 * i + 1;
int parent = i;
while (child < len) {
//小的孩子节点与双亲节点比较
if (child + 1 < len && num[child] > num[child + 1]) {
//找值小的孩子结点下标
child++;
}
if (num[parent] > num[child]) {
//改变了前边部分有序的值,就需要从新与变动过的地方比较
int tem = num[parent];
num[parent] = num[child];
num[child] = tem;
parent = child;//值变动,改变双亲的下标值,再比较
}
else//没有变动则说明当前序列符合小顶堆规则,退出循环比较
break;
child = child * 2 + 1;//更新孩子结点下标
}
}
void buildd_3(int num[], int len) //自上而下建大顶堆
{
for (int i = 1; i < len; i++) {
int j = i;
while (j != 0 && num[j] > num[(j - 1) / 2]) {
int tem = num[j];
num[j] = num[(j - 1) / 2];
num[(j - 1) / 2] = tem;
j = (j - 1) / 2;
}
}
}*/
void buildd_4(int num[], int i, int len) //自下而上建大顶堆
{
int child = 2 * i + 1;
int parent = i;
while (child < len) {
if (child + 1 < len && num[child] < num[child + 1]) {
child++;
}
if (num[parent] < num[child]) {
int tem = num[parent];
num[parent] = num[child];
num[child] = tem;
parent = child;
}
else
break;
child = child * 2 + 1;
}
}
void buildHeap(int array[], int size) {
for (int i = size / 2 - 1; i >= 0; i--) {
//倒数第二排开始,创建大顶堆,必须从下往上比较
buildd__2(array, i, size);
//buildd__4(array, i, size); // 否则有的不符合大顶堆定义
}
}
int main()
{
int num[10] = {
0,12,4,54,3,5,78,34,22,90 };
buildHeap(num, 10);
//自上而下堆排序
for (int i = 9; i >= 0; i--) {
//建好堆后交换根结点和当前序列最后一个节点
int tem = num[i];
num[i] = num[0];
num[0] = tem;
buildd_2(num, 0, i);
//buildd_4(num, 0, i);
}
/* 自上而下堆排序
buildd_1(num, 0, i);
for (int i = 9; i >= 0; i--) {
int tem = num[i];
num[i] = num[0];
num[0] = tem;
buildd_1(num, 0, i);
//buildd_3(num, 0, i);
}*/
for (int i = 0; i < 10; i++) {
printf("%d ", num[i]);
}
return 0;
}
时间复杂度:最坏情况下T(n)=O(nlogn);
辅助空间:S(n)=O(1)。
内部排序基本就这些内容了,朋友们下次见!!!