史上超全内部排序大汇总

6 篇文章 1 订阅
2 篇文章 0 订阅

关注我,你不会吃亏~

在这里插入图片描述

一、排序算法目录

  1. 直接插入排序
  2. 折半插入排序
  3. 希尔排序
  4. 冒泡排序
  5. 快速排序
  6. 归并排序
  7. 选择排序
  8. 堆排序
  9. 计数排序
  10. 桶排序
  11. 基数排序

在这里插入图片描述


附:
  1. 排序代码模板
  2. 排序完整代码框架
  3. 排序代码在线测试
  4. 本站排序模板展现
  5. 数据结构可视化简略展示
  6. 数据结构可视化详细展示

在这里插入图片描述

二、排序对比一览表

在这里插入图片描述

  1. inference1
  2. inference2

到文章末尾

三、插入类排序

1. 直接插入排序

1.1 算法思想

将序列分为有序部分和无序部分,从无序部分中依次选择元素,与有序部分进行比较,找到合适的位置,将原来的元素后移,将元素插入到相应的位置,直到全部记录插入完成。

1.2 动画演示

插入.gif

1.3 时间复杂度
  1. 最好 — O ( n ) O(n) O(n) 序列有序
  2. 最坏 — O ( n 2 ) O(n^2) O(n2) 序列逆序
  3. 平均 — O ( n 2 ) O(n^2) O(n2)
1.4 空间复杂度 O(1)
  1. 不带哨兵用临时变量temp
  2. 带哨兵用a[0]
1.5 稳定性

稳定

1.6 适用性
  1. 顺序表
  2. 链表
1.7 算法特点
  1. 稳定排序
  2. 算法简易,易实现
  3. 顺序表、链表均可实现
  4. 更适用于初始记录基本有序的情况,当n较大时,时间复杂度高不宜采用。
1.8 核心代码
//直接插入排序(无哨兵) 
void InsertSort(int a[], int n){
	int i, j , temp;
	for(i = 1; i < n; i ++){
		if(a[i] < a[i - 1]){
			temp = a[i];
			for(j = i - 1; j >= 0 && temp < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = temp;
		}
	}
}
//直接插入排序(有哨兵) 
void InsertSort2(int a[], int n){
	int i , j;
	for(i = 2; i <= n; i ++){
		if(a[i] < a[i - 1]){
			a[0] = a[i];
			for(j = i - 1; a[0] < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = a[0];
		}
	}
} 
1.9 完整代码(无哨兵)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <fstream>

using namespace std;
const int N = 10;
int data[N], idex;

//直接插入排序(无哨兵) 
void InsertSort(int a[], int n){
	int i, j , temp;
	for(i = 1; i < n; i ++){
		if(a[i] < a[i - 1]){
			temp = a[i];
			for(j = i - 1; j >= 0 && temp < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = temp;
		}
	}
}

int main(){
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt",ios::in);
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt",ios::out);
	if (!infile.is_open()) { 
        cout << "Open file failure" << endl; 
    }
    while(!infile.eof()){
    	infile >> data[idex ++];
	}
	
	//原数组元素 
	for(int i = 0; i < N; i ++) cout << data[i] << ' '; cout << endl;
	
	//不带哨兵的插入排序后 
	InsertSort(data,idex);
	for(int i = 0; i < N; i ++) cout << data[i] << ' '; cout << endl;
	
	for(int i = 0; i < N; i ++)	outfile << data[i] << ' '; outfile << endl;
	
	infile.close();
	outfile.close();
	return 0;
} 
1.10 输入案例(in.txt)
13 69 86 99 59 23 64 32 86 72
1.11 输出结果(out.txt)
13 23 32 59 64 69 72 86 86 99
1.12 完整代码(有哨兵)
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <fstream>

using namespace std;
const int N = 20;
int data[N], idex,num = 10;

//直接插入排序(有哨兵) 
void InsertSort2(int a[], int n){
	int i , j;
	for(i = 2; i <= n; i ++){
		if(a[i] < a[i - 1]){
			a[0] = a[i];
			for(j = i - 1; a[0] < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = a[0];
		}
	}
} 

int main(){
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt",ios::in);
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt",ios::out);
	if (!infile.is_open()) { 
        cout << "Open file failure" << endl; 
    }
	
	idex = 1;
    while(!infile.eof()){
    	infile >> data[idex ++];
	}
	
	//原数组元素 
	for(int i = 1; i <= num; i ++) cout << data[i] << ' '; cout << endl;
	
	//不带哨兵的插入排序后 
	InsertSort2(data,num);
	//sort(data + 1 ,data + num + 1);
	for(int i = 1; i <= num; i ++) cout << data[i] << ' '; cout << endl;
	
	//输出排序后文件数据 
	for(int i = 1; i <= num; i ++)	outfile << data[i] << ' '; outfile << endl;
	
	infile.close();
	outfile.close();
	return 0;
} //直接插入排序(有哨兵)

1.13 输入案例(in.txt)
13 69 86 99 59 23 64 32 86 72
1.14 输出结果(out.txt)
13 23 32 59 64 69 72 86 86 99
返回排序算法目录

2. 折半插入排序

2.1 算法思想

设置三个变量 l o w , h i g h , m i d , low,high,mid, low,high,mid m i d = ( l o w + h i g h ) / 2 mid = (low + high) / 2 mid=(low+high)/2,若 a [ m i d ] > k e y a[mid] > key a[mid]>key, 则令 h i g h = m i d − 1 high = mid - 1 high=mid1,否则令 l o w = m i d + 1 low = mid + 1 low=mid+1,直到 l o w > h i g low > hig low>hig时停止循环。对序列中的每个元素做以上处理,找到合适位置将其他元素后移,进行插入。

2.2 时间复杂度
  1. 最好 — O ( n l o g n ) O(nlogn) O(nlogn)
  2. 最坏 — O ( n 2 ) O(n^2) O(n2)
  3. 平均 — O ( n 2 ) O(n^2) O(n2)
注:比较次数与待排序列的初始装态无关,仅取决于表中的元素个数
2.3 空间复杂度 O(1)
  1. 不带哨兵用临时变量temp
  2. 带哨兵用a[0]
2.4 稳定性

稳定

2.5 适用性

仅适用于顺序表

2.6 算法特点
  1. 稳定排序
  2. 因为要进行折半查找,所以只能用于顺序表,不能用于链表
  3. 适合初始记录无序,n较大的情况。
  4. 先比于直接插入排序,比较次数大大减少。
2.7 核心代码
//折半插入排序 (带哨兵) 
void BinaryInsertSort(int a[], int n){
	int i, j, low, high, mid;
	for(i = 2; i <= n; i ++){
		a[0] = a[i];//将带插入元素赋给哨兵
		low = 1, high = i - 1;//在1~i-1之间进行二分查找 
		while(low <= high){//high > low循环结束
		 mid = low + high >> 1;
		 if(a[0] < a[mid]) high = mid - 1;
		 else low = mid + 1;//即便元素相等也插入其后,保持排序稳定性
		}
		for(j = i - 1; j >= high + 1; j --){// 待插元素应位于high+1处
		 a[j + 1] = a[j];//将有序元素后移 
		}
		a[high + 1] = a[0];//将待插元素插入到正确位置
	}
}
2.8 完整代码
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;
int num, idx;//num存放元素个数 

int data[N]; 

//折半插入排序 (带哨兵) 
void BinaryInsertSort(int a[], int n){
	int i, j, low, high, mid;
	for(i = 2; i <= n; i ++){
		a[0] = a[i];//将带插入元素赋给哨兵
		low = 1, high = i - 1;//在1~i-1之间进行二分查找 
		while(low <= high){//high > low循环结束
		 mid = low + high >> 1;
		 if(a[0] < a[mid]) high = mid - 1;
		 else low = mid + 1;//即便元素相等也插入其后,保持排序稳定性
		}
		for(j = i - 1; j >= high + 1; j --){// 待插元素应位于high+1处
		 a[j + 1] = a[j];//将有序元素后移 
		}
		a[high + 1] = a[0];//将待插元素插入到正确位置
	}
}

int main(){
	//读文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt",ios::in);
	
	//写文件
	ofstream outfile; 
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt",ios::out);
	
	if(!infile.is_open()){//判断文件是否打开成功 
		cout << "file open failure !" << endl;
	}
	
	infile >> num;
	while(num != 0){
		infile >> data[++ idx];
		num --;
	}
	
	num = idx;
	for(int i = 1; i <= num; i ++) cout << data[i] << ' '; cout << endl;
	
	BinaryInsertSort(data, idx);
	//sort(data,data + num);
	for(int i = 1; i <= num; i ++) cout << data[i] << ' '; cout << endl; 
	
	idx = 1;
	outfile << num; outfile << endl;
	while(num != 0){
		outfile << data[idx ++] << ' ';
		num --;
	}
	outfile << endl;
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
} 
2.9 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
2.10 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

3. 希尔排序

3.1 算法思想

先将序列分为若干个子序列,然后对各个子序列进行直接插入排序,等到序列基本有序时,再对整个序列进行一次直接插入排序。即先追求表中的元素部分有序,再逐渐逼近全局有序。

优点:

让关键字值小的元素能能够很快移动到前面,且序列基本有序时进行直接插入排序时间效率会提升很多。

3.2 演示动画

希尔.gif

3.3 时间复杂度
  1. 最好 — 无 无
  2. 最坏 — O ( n 2 ) O(n^2) O(n2)
  3. 平均 — O ( n 1.3 ) O(n^{1.3}) O(n1.3)
3.4 空间复杂度 O(1)

使用数组头号元素a[0]

3.5 稳定性

不稳定

3.6 适用性

仅适用于顺序表

3.7 算法特点
  1. 记录跳跃式地移动导致排序方法是不稳定的
  2. 只能用于顺序结构,不能用于链式结构
  3. 增量序列可以有各种取法,但应该使增量序列中的值没有除1以外的公因子,并且最后一个增量值必须为1
  4. 记录总的比较次数和移动次数都比直接插入排序要少,n越大时,效果越明显。适合初始记录无序,n较大时的情况。
3.8 核心代码
//希尔排序 
void ShellSort(int a[], int n){
	int d, i, j;
	for(d = n / 2; d >= 1; d /= 2){//d为增量,增量序列依次折半(算法原创作者本人推荐) 
		for(i = d + 1; i <= n; i ++){//处理局部序列 
			if(a[i] < a[i - d]){
				a[0] = a[i];//暂存当前待处理元素 
				for(j = i - d; j > 0 && a[0] < a[j]; j -= d){
					a[j + d] = a[j];//局部元素后移 
				}
				a[j + d] = a[0];//在合适位置处插入当前元素 
			}
		}
	}
}
3.9 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx;

//希尔排序 
void ShellSort(int a[], int n){
	int d, i, j;
	for(d = n / 2; d >= 1; d /= 2){//d为增量,增量序列依次折半(算法原创作者本人推荐) 
		for(i = d + 1; i <= n; i ++){//处理局部序列 
			if(a[i] < a[i - d]){
				a[0] = a[i];//暂存当前待处理元素 
				for(j = i - d; j > 0 && a[0] < a[j]; j -= d){
					a[j + d] = a[j];//局部元素后移 
				}
				a[j + d] = a[0];//在合适位置处插入当前元素 
			}
		}
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[++ idx];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 1; i <= idx; i ++) cout << data[i] << ' '; cout << endl;
	
	cout << "使用sort函数排序后序列: " << endl;
	sort(data + 1, data + 1 + idx); 
	for(int i = 1; i <= idx;  i++) cout << data[i] << ' '; cout << endl;
	
	ShellSort(data, idx);
	cout << "使用希尔排序后序列为:" << endl;
	for(int i = 1; i <= idx;  i++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
3.10 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
3.11 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

四、 交换类排序

1. 冒泡排序

1.1 算法思想

从前往后(或从后往前)两两比较相邻元素的值,若为逆序则交换它们,直到序列比较完为止,称这样的过程为一趟冒泡排序,进行n-1趟冒泡排序即可完成排序。

注:
  1. 每一趟排序都可以使一个元素移动到最终位置,已确定最终位置的元素在之后的处理中无需再对比
  2. 如果某一趟排序过程中未发生交换,则算法可提前结束
1.2 演示动画

冒泡.gif

1.3 时间复杂度
  1. 最好 — O ( n ) O(n) O(n) 有序序列
  2. 最坏 — O ( n 2 ) O(n^2) O(n2) 逆序序列
  3. 平均 — O ( n 2 ) O(n^ {2}) O(n2)
1.4 空间复杂度

使用临时变量 ---- O ( 1 ) O(1) O(1)

1.5 稳定性

稳定

1.6 适用性
  1. 顺序表
  2. 链表
1.7 算法特点
  1. 稳定排序
  2. 可用于链式存储和顺序存储
  3. 移动记录的次数比较多,算法平均时间性能比直接插入排序差。当初始序列无序,n 较大时,此算法不宜采用
1.8 核心代码
//冒泡排序 
void BubbleSort(int a[], int n){
	for(int i = 0; i < n - 1; i ++){// 控制趟数, n-1趟 
		bool flag = false;//标记每一趟是否有元素发生交换 
		for(int j = n - 1; j > i; j --){//从后往前依次枚举 
			if(a[j] < a[j - 1]){
			swap(a[j], a[j - 1]);
			flag = true;//发生交换 
			} 
		}
		if(flag == false) return;//本趟未发生交换,即所有元素都已经有序 
	}
}
1.9 优化历程

1.朴素写法

//升序排序
void bubble_sort(int a[], int len){//枚举趟, len为数组长度
    for(int i = 0; i < len; i ++){//枚举比较元素
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){//逆序,交换
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
            }
        }
    }
}

2.一次优化

设置一个标志位, 用来表示当前第 i 趟是否有交换, 如果有则要进行 i+1 趟, 如果没有, 则说明当前数组
已经完成排序, 一旦发现已经排好序, 立即跳出循环, 减少无谓的比较次数.

//升序排序
void bubble_sort(int a[], int len){
    for(int i = 0; i < len; i ++){
        bool flag = true;//记录是否发生交换
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                flag = false;//发生交换
            }
        }
        if(flag) break;//某一趟已经完全排好序,直接退出.
    }
}

3.二次优化

利用一个标志位, 记录一下当前第 i 趟所交换的最后一个位置的下标,在进行第 i+1趟的时候, 只需要内循
环到标记位置就可以了, 因为后面位置上的元素在上一趟中没有换位, 这一次也不可能会换位置了.

//升序排序
void bubble_sort(int a[], int len){
    int pos;
    for(int i = 0; i < len; i ++){
        bool flag = true;
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                pos = j;//记录交换的位置
                flag = false;//发生交换
            }
        }
        len = pos;//记录上一次已比较好的位置,用来更新 len
        if(flag) break;
    }
}

总结:

一次优化主要是针对在中间的某一次已经完全排好序,无需再进行后续比较的情况,增加一个标记,根据标记判断当
前数组是否已经完全排好序,一旦排好序,循环立即退出,减少了后续不必要的比较.

二次优化代码主要是在之前的基础上增加一个pos变量,用于记录上一趟发生交换元素最后一个位置,目的是略过之
前已经排好序的元素,枚举到未排好序的元素为止.

1.10 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx;

//冒泡排序 
void BubbleSort(int a[], int n){
	for(int i = 0; i < n - 1; i ++){// 控制趟数, n-1趟 
		bool flag = false;//标记每一趟是否有元素发生交换 
		for(int j = n - 1; j > i; j --){//从后往前依次枚举 
			if(a[j] < a[j - 1]){
			swap(a[j], a[j - 1]);
			flag = true;//发生交换 
			} 
		}
		if(flag == false) return;//本趟未发生交换,即所有元素都已经有序 
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	BubbleSort(data, idx);
	cout << "使用冒泡排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.11 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.12 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

2. 快速排序

2.1 算法思想

在待排序表 a[0...n-1]中任取一个元素pivot作为枢轴(或基准,通常取首元素),通过一趟排序将待排序表划分为独立的两个部分a[0...k-1]a[k + 1 ...n-1],使得a[0...k-1]中的所有元素均小于pivot,a[k + 1...n - 1]中所有元素均大于等于pivot,则pivot放在其最终的位置a[k]上,这一过程成为一次“划分”,然后分别递归地对两个子表重复上述过程,直到每一部分内只有一个元素或空为止,即所有元素放在了其最终的位置上。

2.2 演示动画

快速.gif

2.3 时间复杂度
  1. 最好 — O ( n l o g n ) O(nlogn) O(nlogn) 序列均匀分割
  2. 最坏 — O ( n 2 ) O(n^2) O(n2) 序列有序
  3. 平均 — O ( n l o g n ) O(nlogn) O(nlogn)
2.4 空间复杂度
  1. 最好 — O ( n l o g n ) O(nlogn) O(nlogn) 序列均匀分割
  2. 最坏 — O ( n ) O(n) O(n) 序列有序
2.5 稳定性

不稳定

2.6 适用性

仅适用于顺序表

2.7 算法特点
  1. 当n较大时,在平均情况下快速排序是所有内部排序方法中速度最快的一种,所以其适合初始记录无序、n较大时的情况
  2. 记录非顺次的移动导致排序方法是不稳定的
  3. 排序过程中需要定位表的上界和下界,所以仅适合于顺序结构,很难适用于链式结构
2.8 核心代码
//划分函数,选取枢轴元素,且定位数轴元素下标 
int Partion(int a[], int low, int high){
	int pivot = a[low];//选取第一个元素作为枢轴,当然也可以选最后一个元素作为枢轴
	while(low < high){//在low和high之间搜索枢轴位置 
		while(low < high && pivot <= a[high]) high --;
		a[low] = a[high];//比枢轴小的移动到左端 
		while(low < high && a[low] <= pivot) low ++;
		a[high] = a[low];//比枢轴大的移动到右端 
	}
	a[low] = pivot;//将枢轴存放到最终位置 
	return low;//返回存放枢轴的最终的位置 
} 
//快速排序 
void QuickSort(int a[], int low, int high){
	if(low < high){//递归跳出条件 
		int pivotpos = Partion(a, low, high);//划分 
		QuickSort(a, low, pivotpos - 1);//划分左子表 
		QuickSort(a, pivotpos + 1, high);//划分右子表 
	}
}

2.9 精简代码
void quick_sort(int q[], int l, int r)
{
    if(l >= r)   return;
    int x = q[l + r >> 1], i = l - 1, j = r + 1;
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j)   swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}

在这里插入图片描述

此乃 y 总毕生锤炼之精华,全网最简,没有之一,不接受反驳,哈哈 ~
2.10 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx;

//划分函数,选取枢轴元素,且定位数轴元素下标 
int Partion(int a[], int low, int high){
	int pivot = a[low];//选取第一个元素作为枢轴 
	while(low < high){//在low和high之间搜索枢轴位置 
		while(low < high && pivot <= a[high]) high --;
		a[low] = a[high];//比枢轴小的移动到左端 
		while(low < high && a[low] <= pivot) low ++;
		a[high] = a[low];//比枢轴大的移动到右端 
	}
	a[low] = pivot;//将枢轴存放到最终位置 
	return low;//返回存放枢轴的最终的位置 
} 

//快速排序 
void QuickSort(int a[], int low, int high){
	if(low < high){//递归跳出条件 
		int pivotpos = Partion(a, low, high);//划分 
		QuickSort(a, low, pivotpos - 1);//划分左子表 
		QuickSort(a, pivotpos + 1, high);//划分右子表 
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	cout << "使用sort函数排序后序列: " << endl;
	sort(data, data + idx); 
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	QuickSort(data, 0, idx - 1);
	cout << "使用快速排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
2.11 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
2.12 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
2.13 经典例题

快速排序

返回排序算法目录

五、 归并类排序

1. 归并排序

1.1 算法思想

将两个或多个有序序列合并为一个有序序列

注:
  1. 二叉树的第 h 层最多有 2h-1 个结点,若树高为 h ,则应该满足 n <= 2h-1 , 即 h − 1 = ⌈ l o g 2 n ⌉ h - 1 = \lceil log{_2}n\rceil h1=log2n, 即趟数 = ⌈ l o g 2 n ⌉ \lceil log{_2}n\rceil log2n
  2. 每趟归并时间复杂度为 O ( n ) O(n) O(n) ,则算法时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
  3. 空间复杂度为 O ( n ) O(n) O(n) ,来自于辅助数组
1.2 演示动画

归并.gif

1.3 时间复杂度
  1. 最好 — O ( n l o g n ) O ( nlogn ) O(nlogn)
  2. 最坏 — O ( n l o g n ) O ( nlogn ) O(nlogn)
  3. 平均 — O ( n l o g n ) O ( nlogn ) O(nlogn)

注: 归并排序分割子序列与初始序列无关,因此它的最好、最坏、平均时间复杂度均为 O ( n l o g n ) O(nlogn) O(nlogn)

1.4 空间复杂度

需要一个辅助数组 ---- O ( n ) O(n) O(n)

1.5 稳定性

稳定

1.6 适用性
  1. 顺序表
  2. 链表
1.7 算法特点
  1. 稳定排序
  2. 可用于顺序结构,也可用于链式结构。使用链式结构不需要附加的存储空间,但递归实现时仍需要开辟相应的递归工作栈。
  3. 一般而言,对于 n n n 个元素进行 k k k 路归并排序时,排序的趟数 m m m 满足 k m = n k^m = n km=n, 从而 m = l o g k n m = log{_k}n m=logkn ,又考虑到 m m m为整数,所以 m = ⌈ l o g k n ⌉ m = \lceil log{_k}n\rceil m=logkn
1.8 核心代码
//a[low ... mid] 和 a[mid + 1 ...high]各自有序,将二者归并 
void Merge(int a[], int low, int mid, int high){
	int k = low;
	int i = low, j = mid + 1;//左、右部分首元素 
	
	//将 a数组数据复制到 b数组 
	for(int i = low; i <= high; i ++) b[i] = a[i];
	
	//将 a[low ... mid] 和 a[mid + 1 ...high]归并
	while(i <= mid && j <= high){ 
		if(b[i] < b[j]) a[k ++] = b[i ++];
		else a[k ++] = b[j ++];
	}
	
	while(i <= mid) a[k ++] = b[i ++];//a数组已经枚举完 
	while(j <= high) a[k ++] = b[j ++];//b数组已经枚举完
}  
// 归并排序 
void MergeSort(int a[], int low, int high){//a[low ...high]
	if(low < high){
		int mid = low + high >> 1;//从中间划分 
		MergeSort(a, low, mid);//对左半部分进行归并排序 
		MergeSort(a, mid + 1, high);//对左半部分进行归并排序 
		Merge(a, low, mid, high);//归并 
	}
}
1.9 极简代码
//归并排序
void merge_sort(int a[], int l, int r){
    if(l >= r) return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid), merge_sort(a, mid + 1, r);//递归划分
    
    int k = 0, i = l, j = mid + 1;
    while(i <= mid && j <= r){
        if(a[i] <= a[j]) q[k ++] = a[i ++];
        else q[k ++] = a[j ++];
    }
    while(i <= mid) q[k ++] = a[i ++];
    while(j <= r) q[k ++] = a[j ++];
    
    for(int i = l, j = 0; i <= r; i ++, j ++) a[i] = q[j];//合并
}

在这里插入图片描述

1.10 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx, b[N];

//a[low ... mid] 和 a[mid + 1 ...high]各自有序,将二者归并 
void Merge(int a[], int low, int mid, int high){
	int k = low;
	int i = low, j = mid + 1;//左、右部分首元素 
	
	//将 a数组数据复制到 b数组 
	for(int i = low; i <= high; i ++) b[i] = a[i];
	
	//将 a[low ... mid] 和 a[mid + 1 ...high]归并
	while(i <= mid && j <= high){ 
		if(b[i] < b[j]) a[k ++] = b[i ++];
		else a[k ++] = b[j ++];
	}
	
	while(i <= mid) a[k ++] = b[i ++];//a数组已经枚举完 
	while(j <= high) a[k ++] = b[j ++];//b数组已经枚举完
}  

// 归并排序 
void MergeSort(int a[], int low, int high){//a[low ...high]
	if(low < high){
		int mid = low + high >> 1;//从中间划分 
		MergeSort(a, low, mid);//对左半部分进行归并排序 
		MergeSort(a, mid + 1, high);//对左半部分进行归并排序 
		Merge(a, low, mid, high);//归并 
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	cout << "使用sort函数排序后序列: " << endl;
	sort(data, data + idx ); //sort排序区间左闭右开 
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	MergeSort(data, 0, idx - 1);
	cout << "使用堆排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.11 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.12 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 

返回排序算法目录

六、选择类排序

1. 简单选择排序

1.1 算法思想

每一趟在待排元素中选取关键字最小的元素加入到有序序列

注:

简单选择排序与序列的初始状态无关,仅与序列的元素个数有关

1.2 演示动画

选择.gif

1.3 时间复杂度
  1. 最好 — O ( n 2 ) O ( n^2) O(n2)
  2. 最坏 — O ( n 2 ) O ( n^2) O(n2)
  3. 平均 — O ( n 2 ) O ( n^2) O(n2)
1.4 空间复杂度

仅用一个临时变量 min — O ( 1 ) O ( 1) O(1)

1.5 稳定性

不稳定

1.6 适用性
  1. 顺序表
  2. 链表
1.7 算法特点
  1. 不稳定,但改变策略可以写出不产生“不稳定现象”的选择排序算法
  2. 可用于链式存储结构
  3. 移动次数较少,当每一记录占用的空间较多时,此方法比直接插入排序快
  4. 选择排序的主要操作是关键字的比较,因此,改进此算法应从减少“比较次数”出发考虑
1.8 核心代码
//交换两个元素 
void swap(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}
//选择排序 
void SelectSort(int a[], int n){
	for(int i = 0; i < n; i ++){
		int min = i;//min记录最下元素的下标 
		for(int j = i; j < n; j ++){
			if(a[j] < a[min]) min = j;
		}
		if(min != i) swap(a[i], a[min]);//交换两个值 
	}
}
1.9 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx;

//交换两个元素 
void swap(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}

//选择排序 
void SelectSort(int a[], int n){
	for(int i = 0; i < n; i ++){
		int min = i;//min记录最下元素的下标 
		for(int j = i; j < n; j ++){
			if(a[j] < a[min]) min = j;
		}
		if(min != i) swap(a[i], a[min]);//交换两个值 
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	cout << "使用sort函数排序后序列: " << endl;
	sort(data, data + idx); 
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	SelectSort(data, idx);
	cout << "使用选择排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.10 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.11 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

2. 堆排序

2.1 算法思想

首先将待排序列构成一个大根堆,然后每一趟将堆顶元素加入到有序子序列(与待排元序列中的最后一个元素交换),并将待排序列再次调整为大根堆(即将小元素 “下坠” )。如此反复执行,最终便能得到一个递增的有序序列。

注:

  1. 基于 “大根堆” 的堆排序得到的是递增序列
  2. 建堆从最后一个非叶子结点开始(叶结点自然不用再调整)
  3. 一个结点每“下坠”一层,最多只需要比对关键字两次
  4. 若树高为h,某结点在第i层,将这个结点向下调整最多只需要“下坠”h - i层,关键字的比对次数不超过2(h - i)
  5. n个结点的完全二叉树,树高 h = ⌊ l o g 2 n ⌋ + 1 h = \lfloor log{_2}n \rfloor + 1 h=log2n+1
2.2 演示动画

堆.gif

2.3 时间复杂度
  1. 最好 — O ( n l o g n ) O(nlogn) O(nlogn)
  2. 最坏 — O ( n l o g n ) O(nlogn) O(nlogn)
  3. 平均 — O ( n l o g n ) O(nlogn) O(nlogn)

注:时间复杂度 = O ( n ) + O ( n l o g 2 n ) = O ( n l o g n ) O(n) + O(nlog_2n) = O(nlogn) O(n)+O(nlog2n)=O(nlogn)

2.4 空间复杂度

O ( 1 ) O(1) O(1)

2.5 稳定性

不稳定

2.6 适用性

仅适用于顺序表

2.7 算法特点
  1. 不稳定排序
  2. 只能用于顺序结构,不能用于链式结构
  3. 初始建堆所需要的比较次数比较多。因此,当记录数较少时,不宜采用。当记录较多时,较为高效。
2.8 核心代码
//将以k为根结点的树调整为大根堆 
void HeadAdjust(int a[], int k, int len){//注:除k结点外其他已经有序 
	a[0] = a[k];//a[0]暂存子树根节点 
	for(int i = 2 * k; i <= len; i *= 2){//沿较大子结点筛选 
		if(i < len && a[i] < a[i + 1]) i ++;//i为较大子结点下标 (i<len表示k有右孩子) 
		if(a[0] >= a[i]) break;//筛选结束 
		else{
			a[k] = a[i];//递归处理子结点 
			k = i;
		}
	}
	a[k] = a[0];
}
//建大根堆 时间复杂度---O(n)  
void BuildHeap(int a[], int len){//从下往上建堆,从最后一个叶子结点的根节点开始 
	for(int i = len / 2; i > 0; i --){//处理所有非终端结点 
		HeadAdjust(a, i , len);
	}
}//建堆过程关键字的比较次数不超过4n(定理) 
//堆排序的完整逻辑 
void HeapSort(int a[], int len){
	BuildHeap(a, len);//建堆 O(n) 
	for(int i = len; i > 1; i --){//从后往前处理 ,共n-1趟交换和调整 
		swap(a[i], a[1]);//将堆顶元素(最大元素)与堆底元素交换 
		HeadAdjust(a, 1, i - 1);//把剩余待排元素调整为堆 
	}
}//每趟时间复杂度不超过o(h) =  O(logn),共 n-1趟 
2.9 建大根堆思路

把所有非终端结点都检查一遍,是否满足大根堆的要求,如果不满足,则进行调整:

  1. 检查当前结点是否满足根 >= 左、右,如果不满足,则将当前结点与更大的一个孩子交换
  2. 若元素互换破坏了下一级的堆,则采用相同的方式继续往下调整(小元素不断“下坠”)
//将以k为根结点的树调整为大根堆 
void HeadAdjust(int a[], int k, int len){//注:除k结点外其他已经有序 
	a[0] = a[k];//a[0]暂存子树根节点 
	for(int i = 2 * k; i <= len; i *= 2){//沿较大子结点筛选 
		if(i < len && a[i] < a[i + 1]) i ++;//i为较大子结点下标 (i<len表示k有右孩子) 
		if(a[0] >= a[i]) break;//筛选结束 
		else{
			a[k] = a[i];//递归处理子结点 
			k = i;
		}
	}
	a[k] = a[0];
}
//建大根堆 时间复杂度---O(n)  
void BuildHeap(int a[], int len){//从下往上建堆,从最后一个叶子结点的根节点开始 
	for(int i = len / 2; i > 0; i --){//处理所有非终端结点 
		HeadAdjust(a, i , len);
	}
}//建堆过程关键字的比较次数不超过4n(定理) 
2.10 堆排序思路

每一趟将堆顶元素加入到有序子序列(与待排列中的最后一个元素交换),并将待排序列再次调整为大根堆(小元素不断“下坠”)

//堆排序的完整逻辑 —— 建堆从最后一个非叶子结点开始(叶结点自然不用调整)
void HeapSort(int a[], int len){
	BuildHeap(a, len);//建堆 O(n) 
	for(int i = len; i > 1; i --){//从后往前处理 ,共n-1趟交换和调整 
		swap(a[i], a[1]);//将堆顶元素(最大元素)与堆底元素交换 
		HeadAdjust(a, 1, i - 1);//把剩余待排元素调整为堆 
	}
}//每趟时间复杂度不超过o(h) =  O(logn),共 n-1趟 
2.11 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 20;

int num;
int data[N],idx;

//将以k为根结点的树调整为大根堆 
void HeadAdjust(int a[], int k, int len){//注:除k结点外其他已经有序 
	a[0] = a[k];//a[0]暂存子树根节点 
	for(int i = 2 * k; i <= len; i *= 2){//沿较大子结点筛选 
		if(i < len && a[i] < a[i + 1]) i ++;//i为较大子结点下标 (i<len表示k有右孩子) 
		if(a[0] >= a[i]) break;//筛选结束 
		else{
			a[k] = a[i];//递归处理子结点 
			k = i;
		}
	}
	a[k] = a[0];
}

//建大根堆 时间复杂度---O(n)  
void BuildHeap(int a[], int len){//从下往上建堆,从最后一个叶子结点的根节点开始 
	for(int i = len / 2; i > 0; i --){//处理所有非终端结点 
		HeadAdjust(a, i , len);
	}
}//建堆过程关键字的比较次数不超过4n(定理) 

//堆排序的完整逻辑 
void HeapSort(int a[], int len){
	BuildHeap(a, len);//建堆 O(n) 
	for(int i = len; i > 1; i --){//从后往前处理 ,共n-1趟交换和调整 
		swap(a[i], a[1]);//将堆顶元素(最大元素)与堆底元素交换 
		HeadAdjust(a, 1, i - 1);//把剩余待排元素调整为堆 
	}
}//每趟时间复杂度不超过o(h) =  O(logn),共 n-1趟 

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[++ idx];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 1; i <= idx; i ++) cout << data[i] << ' '; cout << endl;
	
	cout << "使用sort函数排序后序列: " << endl;
	sort(data + 1, data + 1 + idx); 
	for(int i = 1; i <= idx; i ++) cout << data[i] << ' '; cout << endl;
	
	HeapSort(data, idx);
	cout << "使用堆排序后序列为:" << endl;
	for(int i = 1; i <= idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
2.12 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
2.13 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

七、非比较类排序

1. 计数排序

1.1 算法思想

首先,扫描一下整个原始序列 a a a,获取最小值 m i n min min 和最大值 m a x max max,其次开辟一块新的存储空间 b [ d ] b [ d ] b[d],该数组长度为 d = m a x − m i n + 1 d = max - min + 1 d=maxmin+1 b [ d ] b[d] b[d]中存储的是在min ~ max之间的相应数值出现的次数,如: b [ 5 ] = 2 b[5] = 2 b[5]=2,表示 5 5 5 出现 2 2 2 次,最后根据 b b b 数组的统计的元素次数,依次输出各元素。

inference

1.2 演示动画

计数.gif

1.3 时间复杂度
  1. 最好 — O ( n + m ) O(n + m) O(n+m)
  2. 最坏 — O ( n + m ) O(n + m) O(n+m)
  3. 平均 — O ( n + m ) O(n + m) O(n+m)
1.4 空间复杂度

O ( m ) O(m) O(m)

1.5 稳定性

稳定、不稳定均可设定

1.6 算法特点

1.当数列最大最小值差距过大时,并不适用计数排序。

比如给定20个随机整数,范围在0到1亿之间,这时候如果使用计数排序,需要创建长度1亿的数组。不但严重浪费空间,而且时间复杂度也随之升高。

2.当数列元素不是整数,并不适用计数排序。

如果数列中的元素都是小数,比如25.213,或是0.00000001这样,则无法创建对应的统计数组。这样显然无法进行计数排序。

1.7 算法适用性
  1. 顺序表
  2. 链表
1.8 朴素版特点
  1. 代码简洁、易实现
  2. 由于根据最大值开辟存储空间,可能造成空间的浪费,因此耗费存储空间大
  3. 无法保证元素的稳定性
1.9 核心代码
//朴素版 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int cnt[maxval + 1] = {0};//根据最大值开辟新数组空间 
	
	for(int i = 0; i < n; i ++)	cnt[a[i]] ++;//统计原数组中元素出现的次数 

	for(int i = minval, k = 0; i <= maxval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}
1.10 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 200;//注意数组越界问题,开大点 

int num;
int data[N],idx;

//朴素版 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int cnt[maxval + 1] = {0};//根据最大值开辟新数组空间 
	
	for(int i = 0; i < n; i ++)	cnt[a[i]] ++;//统计原数组中元素出现的次数 

	for(int i = minval, k = 0; i <= maxval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	CountSort(data, idx);
	cout << "使用计数排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.11 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.12 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 

1.13 一次优化特点
  1. 相比于朴素版, 空间复杂度大大降低
  2. 不能保证稳定性
1.14 核心代码
//一次优化 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 

	for(int i = 0, k = 0; i <= maxval - minval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i + minval;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}
1.15 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 200;//注意数组越界问题,开大点 

int num;
int data[N],idx;

//一次优化 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 

	for(int i = 0, k = 0; i <= maxval - minval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i + minval;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	CountSort(data, idx);
	cout << "使用计数排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.16 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.17 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 

1.18 二次优化特点
  1. 相比于朴素版, 空间复杂度大大降低
  2. 可以保证稳定性,该排序稳定
1.19 核心代码
//二次优化 
void CountSort(int a[], int n){
	//遍历数组求得最大值和最小值
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){ 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 
	
	int sum = 0;
	for(int i = 0; i < d; i ++){//本质为前缀和数组,用于求位次 
		sum += cnt[i];//此处的cnt既为元素又为之前的元素和,即cnt[i] = cnt[i] + cnt[i - 1] 
		cnt[i] = sum;// 比如, cnt[5] = 3,表示分数95, 排名第 3 
	} 

	int sortArray[d];//sortArray[]存元素真实序列 
	for(int i = n - 1; i >= 0; i --){//将原数组元素从后往前遍历 
		sortArray[cnt[a[i] - minval] - 1] = a[i];
		cnt[a[i] - minval] --;
	}
	
	//将排序后的序列赋给原数组
	for(int i = 0, k = 0; i < d; i ++){
		data[k ++] = sortArray[i];
	}
}
1.20 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

using namespace std;
const int N = 200;//注意数组越界问题,开大点 

int num;
int data[N],idx;

//二次优化 
void CountSort(int a[], int n){
	//遍历数组求得最大值和最小值
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){ 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 
	
	int sum = 0;
	for(int i = 0; i < d; i ++){//本质为前缀和数组,用于求位次 
		sum += cnt[i];//此处的cnt既为元素又为之前的元素和,即cnt[i] = cnt[i] + cnt[i - 1] 
		cnt[i] = sum;// 比如, cnt[5] = 3,表示分数95, 排名第 3 
	} 

	int sortArray[d];//sortArray[]存元素真实序列 
	for(int i = n - 1; i >= 0; i --){//将原数组元素从后往前遍历 
		sortArray[cnt[a[i] - minval] - 1] = a[i];
		cnt[a[i] - minval] --;
	}
	
	//将排序后的序列赋给原数组
	for(int i = 0, k = 0; i < d; i ++){
		data[k ++] = sortArray[i];
	}
}

int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	CountSort(data, idx);
	cout << "使用计数排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
1.21 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
1.22 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

2. 桶排序


2.1 算法思想

首先, 遍历原始序列确定最大值 m a x v a l maxval maxval 和最小值 m i n v a l minval minval,并确定桶的个数 n n n; 然后,将待排序集合中处于同一个值域的元素存入同一个桶中,在桶内使用各种现有的算法进行排序; 最后按照从小到大的顺序依次收集桶中的每一个元素, 即为最终结果。

inference
inference

2.2 演示动画

桶.gif

2.3 时间复杂度
  1. 最好 — O ( n + k ) O(n + k) O(n+k)
  2. 最坏 — O ( n 2 ) O(n^2) O(n2)
  3. 平均 — O ( n + k ) O(n + k) O(n+k)

注: 其 中 k = n l o g ( n / m ) 其中 k = nlog(n / m) k=nlog(n/m)

2.4 空间复杂度

O ( n + k ) O(n + k) O(n+k)

注: 其 中 k = n l o g ( n / m ) 其中 k = nlog(n / m) k=nlog(n/m)

2.5 稳定性

稳定

2.6 算法特点
  1. 桶排序是一种用空间换取时间的排序
  2. 桶排序并非像常规排序那样没有限制,桶排序有相当的限制。因为桶的个数和大小都是我们人为设置的。而每个桶又要避免空桶的情况。所以我们在使用桶排序的时候即需要对待排序数列要求偏均匀,又要要求桶的设计兼顾效率和空间。
  3. 数要相对均匀分布,桶的个数也要合理设计。在设计桶排序时,需要知道输入数据的上界和下界,根据数据的分布情况考虑是否用桶排序,当然如果能用好桶排序,效率还是很高的!
2.7 算法适用性
  1. 顺序表
  2. 链表
2.8 核心代码
//桶排序 
void BucketSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//寻找原序列数组元素的最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int bnum = 10;//桶中元素个数 
	int m = (maxval - minval) / bnum + 1;//桶的个数 
	vector< vector<int> > bucket(m);
	
	//收集,将元素入相应的桶中. 减偏移量是为了将元素映射到更小的区间内,省内存 
	for(int i = 0; i < n; i ++) bucket[(a[i] - minval) / bnum].push_back(a[i]);
	
	//将桶内元素排序 
	for(int i = 0; i < m; i ++) sort(bucket.begin(), bucket.end());
	
	//收集, 将各个桶中的元素收集到一起 
	for(int i = 0, k = 0; i < m; i ++){
		for(int j = 0; j < bucket[i].size(); j ++){
			data[k ++] = bucket[i][j];
		}
	}
}
2.9 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <vector>
 
using namespace std;
const int N = 200;

int data[N], num, idx; 

//桶排序 
void BucketSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//寻找原序列数组元素的最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int bnum = 10;//桶中元素个数 
	int m = (maxval - minval) / bnum + 1;//桶的个数 
	vector< vector<int> > bucket(m);
	
	//收集,将元素入相应的桶中. 减偏移量是为了将元素映射到更小的区间内,省内存 
	for(int i = 0; i < n; i ++) bucket[(a[i] - minval) / bnum].push_back(a[i]);
	
	//将桶内元素排序 
	for(int i = 0; i < m; i ++) sort(bucket.begin(), bucket.end());
	
	//收集, 将各个桶中的元素收集到一起 
	for(int i = 0, k = 0; i < m; i ++){
		for(int j = 0; j < bucket[i].size(); j ++){
			data[k ++] = bucket[i][j];
		}
	}
}
 
int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	BucketSort(data, idx);
	cout << "使用桶排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
2.10 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
2.11 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
返回排序算法目录

3. 基数排序


3.1 算法思想

将整个关键字拆分为d位(或“组”), 按照各个关键字权重递增的次序(如:个、十、百), 做 d d d 趟“分配”和“收集”, 若当前处理的关键字位可能取得 r r r 个值,则需要建立 r r r 个队列,顺序扫描各个元素, 根据当前处理的关键字位, 将元素插入到相应的队列, 把各个队列中的结点,依次出队并连接。

inference
inference

3.2 演示动画

基.gif

3.3 时间复杂度
  1. 最好 — O ( d ( n + r ) ) O(d(n + r)) O(d(n+r))
  2. 最坏 — O ( d ( n + r ) ) O(d(n + r)) O(d(n+r))
  3. 平均 — O ( d ( n + r ) ) O(d(n + r)) O(d(n+r))

注:

  1. r r r 为 基数, 例如:十进制 r = 10 r = 10 r=10
  2. n n n 表示有 n n n 个记录
  3. d d d 表示每个记录含的关键字个数
  4. 时间复杂度与序列初始状态无关
3.4 空间复杂度

O ( r ) O(r) O(r)

3.5 稳定性

稳定

3.6 算法特点
  1. 稳定排序
  2. 可用于链式结构和顺序结构
  3. 时间复杂度可以突破关键字比较方法的下界, O ( n l o g n ) O(nlogn) O(nlogn) 达到 O ( n ) O(n) O(n)
  4. 技术排序使用条件严格,必须知道各级关键字主次关系和各级关键字的取值
3.7 算法适用性
  1. 顺序表
  2. 链表
3.8 朴素版(不可以对负数排序)
3.9 核心代码
//基数排序 
void RadixSort(int a[], int n){
	const int RADIX = 10;//DADIX表示为十进制数 
	int maxval = a[0], minval = a[0], base[10];//base数组存的是十进制数的位权 
	vector< vector<int> > bucket(RADIX);//相当于定义10个桶 
	
	base[0] = 1;//个位的权值为 1 
	for(int i = 1; i < 10; i ++) base[i] = base[i - 1] * 10;//初始化权值 
	for(int i = 0; i < n; i ++){
		maxval = max(maxval, a[i]);//求出所有元素的最大值 
	}

	int dnum = ceil(log10(maxval + 1));//求出最大的位数,比如38为2位 

	for(int d = 0; d < dnum;  d ++){//枚举每一位,共dnum趟 
		for(int i = 0; i < n; i ++){//枚举每一个数 
			int t = (a[i] / base[d]) % RADIX;//取出该位数值,即对应的桶号 
			bucket[t].push_back(a[i]);//将当前数值放入桶中(对应位) 
		}

		for(int i = 0, j = 0, k = 0; i < n; ){//i为当前元素下标,j为桶的编号
			if(k < bucket[j].size()){//k为桶中的第k号元素 
				a[i ++] = bucket[j][k ++];//依次将桶中元素存入数组(已按当前位有序) 
			}
			else j ++, k = 0;//枚举下一个桶,从该桶中第0号元素开始 
		}
		for(auto &v : bucket) v.clear();//将桶中元素清零, 开始下一趟排序 
	}
}
3.10 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <vector>
 
using namespace std;
const int N = 10000;

int data[N], num, idx; 

//基数排序 
void RadixSort(int a[], int n){
	const int RADIX = 10;//DADIX表示为十进制数 
	int maxval = a[0], minval = a[0], base[10];//base数组存的是十进制数的位权 
	vector< vector<int> > bucket(RADIX);//相当于定义10个桶 
	
	base[0] = 1;//个位的权值为 1 
	for(int i = 1; i < 10; i ++) base[i] = base[i - 1] * 10;//初始化权值 
	for(int i = 0; i < n; i ++){
		maxval = max(maxval, a[i]);//求出所有元素的最大值 
	}

	int dnum = ceil(log10(maxval + 1));//求出最大的位数,比如38为2位 

	for(int d = 0; d < dnum;  d ++){//枚举每一位,共dnum趟 
		for(int i = 0; i < n; i ++){//枚举每一个数 
			int t = (a[i] / base[d]) % RADIX;//取出该位数值,即对应的桶号 
			bucket[t].push_back(a[i]);//将当前数值放入桶中(对应位) 
		}

		for(int i = 0, j = 0, k = 0; i < n; ){//i为当前元素下标,j为桶的编号
			if(k < bucket[j].size()){//k为桶中的第k号元素 
				a[i ++] = bucket[j][k ++];//依次将桶中元素存入数组(已按当前位有序) 
			}
			else j ++, k = 0;//枚举下一个桶,从该桶中第0号元素开始 
		}
		for(auto &v : bucket) v.clear();//将桶中元素清零, 开始下一趟排序 
	}
}
 
int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	RadixSort(data, idx);
	cout << "使用基数排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
3.11 输入数据(in.txt)
10
13 69 86 99 59 23 64 32 86 72
3.12 输出数据(out.txt)
10
13 23 32 59 64 69 72 86 86 99 
3.13 优化版(不可以对负数排序)
3.14 核心代码
//基数排序 
void RadixSort(int a[], int n){
	const int RADIX = 10;//DADIX表示为十进制数 
	int maxval = a[0], minval = a[0], base[10];//base数组存的是十进制数的位权 
	vector< vector<int> > bucket(RADIX);//相当于定义10个桶 
	
	base[0] = 1;//个位的权值为 1 
	for(int i = 1; i < 10; i ++) base[i] = base[i - 1] * 10;//初始化权值 
	for(int i = 0; i < n; i ++){
		maxval = max(maxval, a[i]);//求出所有元素的最大值 
		minval = min(minval, a[i]);
	}
	
	//将所有元素右移(即映射到非负数区域) 
	if(minval < 0){
		for(int i = 0; i < n; i ++){
			a[i] -= minval;
		}
		maxval -= minval;//之前的最大值发生变化,更新原最大值 
	}
	
	int dnum = ceil(log10(maxval + 1));//求出最大的位数,比如38为2位 

	for(int d = 0; d < dnum;  d ++){//枚举每一位,共dnum趟 
		for(int i = 0; i < n; i ++){//枚举每一个数 
			int t = (a[i] / base[d]) % RADIX;//取出该位数值,即对应的桶号 
			bucket[t].push_back(a[i]);//将当前数值放入桶中(对应位) 
		}

		for(int i = 0, j = 0, k = 0; i < n; ){//i为当前元素下标,j为桶的编号
			if(k < bucket[j].size()){//k为桶中的第k号元素 
				a[i ++] = bucket[j][k ++];//依次将桶中元素存入数组(已按当前位有序) 
			}
			else j ++, k = 0;//枚举下一个桶,从该桶中第0号元素开始 
		}
		for(auto &v : bucket) v.clear();//将桶中元素清零, 开始下一趟排序 
	}
	
	//还原之前的元素序列,即将元素整体再左移回去 
	if(minval < 0){
		for(int i = 0; i < n; i ++){
			a[i] += minval;
		}
	}
}
3.15 完整代码
#include <iostream> 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <vector>
 
using namespace std;
const int N = 10010;

int data[N], num, idx; 

//基数排序
void RadixSort(int a[], int n){
	const int RADIX = 10;//DADIX表示为十进制数 
	int maxval = a[0], minval = a[0], base[10];//base数组存的是十进制数的位权 
	vector< vector<int> > bucket(RADIX);//相当于定义10个桶 
	
	base[0] = 1;//个位的权值为 1 
	for(int i = 1; i < 10; i ++) base[i] = base[i - 1] * 10;//初始化权值 
	for(int i = 0; i < n; i ++){
		maxval = max(maxval, a[i]);//求出所有元素的最大值 
		minval = min(minval, a[i]);
	}
	
	//将所有元素右移(即映射到非负数区域) 
	if(minval < 0){
		for(int i = 0; i < n; i ++){
			a[i] -= minval;
		}
		maxval -= minval;//之前的最大值发生变化,更新原最大值 
	}
	
	int dnum = ceil(log10(maxval + 1));//求出最大的位数,比如38为2位 

	for(int d = 0; d < dnum;  d ++){//枚举每一位,共dnum趟 
		for(int i = 0; i < n; i ++){//枚举每一个数 
			int t = (a[i] / base[d]) % RADIX;//取出该位数值,即对应的桶号 
			bucket[t].push_back(a[i]);//将当前数值放入桶中(对应位) 
		}

		for(int i = 0, j = 0, k = 0; i < n; ){//i为当前元素下标,j为桶的编号
			if(k < bucket[j].size()){//k为桶中的第k号元素 
				a[i ++] = bucket[j][k ++];//依次将桶中元素存入数组(已按当前位有序) 
			}
			else j ++, k = 0;//枚举下一个桶,从该桶中第0号元素开始 
		}
		for(auto &v : bucket) v.clear();//将桶中元素清零, 开始下一趟排序 
	}
	
	//还原之前的元素序列,即将元素整体再左移回去 
	if(minval < 0){
		for(int i = 0; i < n; i ++){
			a[i] += minval;
		}
	}
}
int main(){
	//打开文件 
	ifstream infile;
	infile.open("D:\\学习\\数据结构\\第8章排序\\in.txt", ios::in);
	
	//写文件 
	ofstream outfile;
	outfile.open("D:\\学习\\数据结构\\第8章排序\\out.txt", ios::out);
	
	if(!infile.is_open()){//判断文件打开是否成功 
		cout << "file open failure!" << endl;
	}
	
	infile >> num;//读取元素个数 
	while(num --){//将文件中的元素复制到data[1...n] 数组中
		infile >> data[idx ++];
	}
	
	cout << "排序前元素序列:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	//cout << "使用sort函数排序后序列: " << endl;
	//sort(data, data + idx); 
	//for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	RadixSort(data, idx);
	cout << "使用基数排序后序列为:" << endl;
	for(int i = 0; i < idx; i ++) cout << data[i] << ' '; cout << endl;
	
	num = idx, idx = 0, outfile << num << endl;//写入数据数num以及在行末写入\n 
	while(num --){
		outfile << data[++ idx] << ' ';//将排序后的数据写到文件中 
	}
	outfile << endl;//向文件末尾写入\n结束符 
	
	//关闭文件 
	infile.close();
	outfile.close();
	return 0;
}
3.16 输入数据(in.txt)
10
13 69 -86 99 59 23 64 32 86 -72
3.17 输出数据(out.txt)
10
-86 -72 13 23 32 59 64 69 86 99 
返回排序算法目录

八、排序完整代码框架

排序代码模板
排序代码在线测试

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
const int N = 100010;

int a[N];
int n;

//此处粘贴相关的排序函数

int main(){
	cin >> n;
	for(int i = 0; i < n; i ++) scanf("%d", &a[i]);
	
	//此处粘贴调用排序的函数, 如:quick_sort(a, 0, n - 1) ;
	
	for(int i = 0; i < n; i ++) printf("%d ", a[i]);
	
	return 0;
}
返回排序算法目录

九、本站模板展现

9.1 直接插入排序
//直接插入排序(无哨兵) 
void InsertSort(int a[], int n){
	int i, j , temp;
	for(i = 1; i < n; i ++){
		if(a[i] < a[i - 1]){
			temp = a[i];
			for(j = i - 1; j >= 0 && temp < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = temp;
		}
	}
}
//直接插入排序(有哨兵) 
void InsertSort2(int a[], int n){
	int i , j;
	for(i = 2; i <= n; i ++){
		if(a[i] < a[i - 1]){
			a[0] = a[i];
			for(j = i - 1; a[0] < a[j]; j --){
				a[j + 1] = a[j];
			}
			a[j + 1] = a[0];
		}
	}
} 
返回排序算法目录

9.2 折半插入排序
//折半插入排序 (带哨兵) 
void BinaryInsertSort(int a[], int n){
	int i, j, low, high, mid;
	for(i = 2; i <= n; i ++){
		a[0] = a[i];//将带插入元素赋给哨兵
		low = 1, high = i - 1;//在1~i-1之间进行二分查找 
		while(low <= high){//high > low循环结束
		 mid = low + high >> 1;
		 if(a[0] < a[mid]) high = mid - 1;
		 else low = mid + 1;//即便元素相等也插入其后,保持排序稳定性
		}
		for(j = i - 1; j >= high + 1; j --){// 待插元素应位于high+1处
		 a[j + 1] = a[j];//将有序元素后移 
		}
		a[high + 1] = a[0];//将待插元素插入到正确位置
	}
}
返回排序算法目录

9.3 希尔排序
//希尔排序 
void ShellSort(int a[], int n){
	int d, i, j;
	for(d = n / 2; d >= 1; d /= 2){//d为增量,增量序列依次折半(算法原创作者本人推荐) 
		for(i = d + 1; i <= n; i ++){//处理局部序列 
			if(a[i] < a[i - d]){
				a[0] = a[i];//暂存当前待处理元素 
				for(j = i - d; j > 0 && a[0] < a[j]; j -= d){
					a[j + d] = a[j];//局部元素后移 
				}
				a[j + d] = a[0];//在合适位置处插入当前元素 
			}
		}
	}
}
返回排序算法目录

9.4 冒泡排序
//冒泡排序 
void BubbleSort(int a[], int n){
	for(int i = 0; i < n - 1; i ++){// 控制趟数, n-1趟 
		bool flag = false;//标记每一趟是否有元素发生交换 
		for(int j = n - 1; j > i; j --){//从后往前依次枚举 
			if(a[j] < a[j - 1]){
			swap(a[j], a[j - 1]);
			flag = true;//发生交换 
			} 
		}
		if(flag == false) return;//本趟未发生交换,即所有元素都已经有序 
	}
}

1.朴素写法

//升序排序
void bubble_sort(int a[], int len){//枚举趟, len为数组长度
    for(int i = 0; i < len; i ++){//枚举比较元素
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){//逆序,交换
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
            }
        }
    }
}

2.一次优化

设置一个标志位, 用来表示当前第 i 趟是否有交换, 如果有则要进行 i+1 趟, 如果没有, 则说明当前数组
已经完成排序, 一旦发现已经排好序, 立即跳出循环, 减少无谓的比较次数.

//升序排序
void bubble_sort(int a[], int len){
    for(int i = 0; i < len; i ++){
        bool flag = true;//记录是否发生交换
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){
                cnt ++;
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                flag = false;//发生交换
            }
        }
        if(flag) break;//某一趟已经完全排好序,直接退出.
    }
}

3.二次优化

利用一个标志位, 记录一下当前第 i 趟所交换的最后一个位置的下标,在进行第 i+1趟的时候, 只需要内循
环到标记位置就可以了, 因为后面位置上的元素在上一趟中没有换位, 这一次也不可能会换位置了.

//升序排序
void bubble_sort(int a[], int len){
    int pos;
    for(int i = 0; i < len; i ++){
        bool flag = true;
        for(int j = 0; j < len - i - 1; j ++){
            if(a[j] > a[j + 1]){
                cnt ++;
                int t = a[j];
                a[j] = a[j + 1];
                a[j + 1] = t;
                pos = j;//记录交换的位置
                flag = false;//发生交换
            }
        }
        len = pos;//记录上一次已比较好的位置,用来更新 len
        if(flag) break;
    }
}
返回排序算法目录

9.5 快速排序
void quick_sort(int q[], int l, int r)
{
    if(l >= r)   return;
    int x = q[l + r >> 1], i = l - 1, j = r + 1;
    while(i < j)
    {
        do i++; while(q[i] < x);
        do j--; while(q[j] > x);
        if(i < j)   swap(q[i], q[j]);
    }
    quick_sort(q, l, j);
    quick_sort(q, j + 1, r);
}
返回排序算法目录

9.6 选择排序
//交换两个元素 
void swap(int &a, int &b){
	int temp = a;
	a = b;
	b = temp;
}
//选择排序 
void SelectSort(int a[], int n){
	for(int i = 0; i < n; i ++){
		int min = i;//min记录最下元素的下标 
		for(int j = i; j < n; j ++){
			if(a[j] < a[min]) min = j;
		}
		if(min != i) swap(a[i], a[min]);//交换两个值 
	}
}
返回排序算法目录

9.7 归并排序
//归并排序
void merge_sort(int a[], int l, int r){
    if(l >= r) return;
    int mid = l + r >> 1;
    merge_sort(a, l, mid), merge_sort(a, mid + 1, r);//递归划分
    
    int k = 0, i = l, j = mid + 1;
    while(i <= mid && j <= r){
        if(a[i] <= a[j]) q[k ++] = a[i ++];
        else q[k ++] = a[j ++];
    }
    while(i <= mid) q[k ++] = a[i ++];
    while(j <= r) q[k ++] = a[j ++];
    
    for(int i = l, j = 0; i <= r; i ++, j ++) a[i] = q[j];//合并
}
返回排序算法目录

9.8 堆排序
//将以k为根结点的树调整为大根堆 
void HeadAdjust(int a[], int k, int len){//注:除k结点外其他已经有序 
	a[0] = a[k];//a[0]暂存子树根节点 
	for(int i = 2 * k; i <= len; i *= 2){//沿较大子结点筛选 
		if(i < len && a[i] < a[i + 1]) i ++;//i为较大子结点下标 (i<len表示k有右孩子) 
		if(a[0] >= a[i]) break;//筛选结束 
		else{
			a[k] = a[i];//递归处理子结点 
			k = i;
		}
	}
	a[k] = a[0];
}
//建大根堆 时间复杂度---O(n)  
void BuildHeap(int a[], int len){//从下往上建堆,从最后一个叶子结点的根节点开始 
	for(int i = len / 2; i > 0; i --){//处理所有非终端结点 
		HeadAdjust(a, i , len);
	}
}//建堆过程关键字的比较次数不超过4n(定理) 
//堆排序的完整逻辑 
void HeapSort(int a[], int len){
	BuildHeap(a, len);//建堆 O(n) 
	for(int i = len; i > 1; i --){//从后往前处理 ,共n-1趟交换和调整 
		swap(a[i], a[1]);//将堆顶元素(最大元素)与堆底元素交换 
		HeadAdjust(a, 1, i - 1);//把剩余待排元素调整为堆 
	}
}//每趟时间复杂度不超过o(h) =  O(logn),共 n-1趟 

返回排序算法目录

9.9 计数排序
//朴素版 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int cnt[maxval + 1] = {0};//根据最大值开辟新数组空间 
	
	for(int i = 0; i < n; i ++)	cnt[a[i]] ++;//统计原数组中元素出现的次数 

	for(int i = minval, k = 0; i <= maxval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}
//一次优化 
void CountSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//遍历数组求得最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 

	for(int i = 0, k = 0; i <= maxval - minval; i ++){
		while(cnt[i] != 0){
			data[k ++] = i + minval;//将排序后的序列赋给原数组 
			cnt[i] --;//i出现的次数减1 
		}
	}
}
//二次优化 
void CountSort(int a[], int n){
	//遍历数组求得最大值和最小值
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){ 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int d = maxval - minval + 1;// 计数数组的实际长度 
	int cnt[d] = {0};//根据最大值开辟新数组空间 
	
	//统计原数组中元素出现的次数
	for(int i = 0; i < n; i ++)	cnt[a[i] - minval] ++;//将元素映射到a[0...d-1] 
	
	int sum = 0;
	for(int i = 0; i < d; i ++){//本质为前缀和数组,用于求位次 
		sum += cnt[i];//此处的cnt既为元素又为之前的元素和,即cnt[i] = cnt[i] + cnt[i - 1] 
		cnt[i] = sum;// 比如, cnt[5] = 3,表示分数95, 排名第 3 
	} 

	int sortArray[d];//sortArray[]存元素真实序列 
	for(int i = n - 1; i >= 0; i --){//将原数组元素从后往前遍历 
		sortArray[cnt[a[i] - minval] - 1] = a[i];
		cnt[a[i] - minval] --;
	}
	
	//将排序后的序列赋给原数组
	for(int i = 0, k = 0; i < d; i ++){
		data[k ++] = sortArray[i];
	}
}
返回排序算法目录

9.10 桶排序
//桶排序 
void BucketSort(int a[], int n){
	int minval = a[0], maxval = a[0];
	for(int i = 0; i < n; i ++){//寻找原序列数组元素的最大值和最小值 
		minval = min(minval, a[i]);
		maxval = max(maxval, a[i]);
	}
	
	int bnum = 10;//桶中元素个数 
	int m = (maxval - minval) / bnum + 1;//桶的个数 
	vector< vector<int> > bucket(m);
	
	//收集,将元素入相应的桶中. 减偏移量是为了将元素映射到更小的区间内,省内存 
	for(int i = 0; i < n; i ++) bucket[(a[i] - minval) / bnum].push_back(a[i]);
	
	//将桶内元素排序 
	for(int i = 0; i < m; i ++) sort(bucket.begin(), bucket.end());
	
	//收集, 将各个桶中的元素收集到一起 
	for(int i = 0, k = 0; i < m; i ++){
		for(int j = 0; j < bucket[i].size(); j ++){
			data[k ++] = bucket[i][j];
		}
	}
}
返回排序算法目录

9.11 基数排序排序
//基数排序 
void RadixSort(int a[], int n){
	const int RADIX = 10;//DADIX表示为十进制数 
	int maxval = a[0], minval = a[0], base[10];//base数组存的是十进制数的位权 
	vector< vector<int> > bucket(RADIX);//相当于定义10个桶 
	
	base[0] = 1;//个位的权值为 1 
	for(int i = 1; i < 10; i ++) base[i] = base[i - 1] * 10;//初始化权值 
	for(int i = 0; i < n; i ++){
		maxval = max(maxval, a[i]);//求出所有元素的最大值 
	}

	int dnum = ceil(log10(maxval + 1));//求出最大的位数,比如38为2位 

	for(int d = 0; d < dnum;  d ++){//枚举每一位,共dnum趟 
		for(int i = 0; i < n; i ++){//枚举每一个数 
			int t = (a[i] / base[d]) % RADIX;//取出该位数值,即对应的桶号 
			bucket[t].push_back(a[i]);//将当前数值放入桶中(对应位) 
		}

		for(int i = 0, j = 0, k = 0; i < n; ){//i为当前元素下标,j为桶的编号
			if(k < bucket[j].size()){//k为桶中的第k号元素 
				a[i ++] = bucket[j][k ++];//依次将桶中元素存入数组(已按当前位有序) 
			}
			else j ++, k = 0;//枚举下一个桶,从该桶中第0号元素开始 
		}
		for(auto &v : bucket) v.clear();//将桶中元素清零, 开始下一趟排序 
	}
}

在这里插入图片描述

谢谢观看, 欢迎交流、支持点赞和收藏哦 ~
返回排序算法目录

  • 16
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

soyisou

您的鼓励将是我创作的最大动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值