深入理解数据结构——排序算法C++实现

目录

一、插入排序

(1)直接插入排序

(2)折半排序

(3)希尔排序

二、交换排序

(1)冒泡排序

(2)快速排序

三、选择排序

(1)直接选择排序

(2)堆排序

四、归并排序

(1)二路归并排序

五、基数排序(桶排序)

(1)二维数组桶排序

(2)链式存储结构桶排序


0总览

参考1:数据结构教程——清华大学出版社

参考2:https://www.cnblogs.com/l199616j/p/10742603.html

参考3:https://cloud.tencent.com/developer/article/1505445

参考4:https://blog.csdn.net/weixin_46554582/article/details/119172449

1、内部排序方法比较

排序方法最好情况平均时间最坏情况辅助空间稳定性复杂性
直接插入O(n)O(n^2)O(n^2)O(l)稳定简单
折半插入

 O(nlog_{2}^{n}

 O(nlog_{2}^{n}

 O(nlog_{2}^{n}

O(l)稳定简单
直接选择O(n^2)O(n^2)O(n^2)O(l)不稳定简单
冒泡排序O(n)O(n^2)O(n^2)O(l)稳定简单
希尔排序

O(nlog_{2}^{n}) 

O(nlog_{2}^{n})  

O(nlog_{2}^{n}) 

O(l)不稳定较复杂
快速排序

O(nlog_{2}^{n}

O(nlog_{2}^{n})  

O(n^2)

O(log_{2}^{n})

不稳定较复杂
二路归并

O(nlog_{2}^{n}) 

O(nlog_{2}^{n})  

O(nlog_{2}^{n}) 

O(n)稳定简单

堆排序

O(nlog_{2}^{n}) 

O(nlog_{2}^{n})  

O(nlog_{2}^{n}) 

O(l)不稳定较复杂
树形选择

O(nlog_{2}^{n}) 

O(nlog_{2}^{n})  

O(nlog_{2}^{n}) 

O(n)不稳定较复杂
基数排序O(2mn)O(2mn)O(2mn)O(dn)稳定较复杂

1、堆排序的辅助空间的要求比快速排序低、并且堆排序不会出现快速排序最差的情况。

2、快速排序的平均性能最好,遇到逆序列时退化为冒泡排序。

3、希尔排序借鉴了插入排序,将序列分成多个部分,每一个部分记录个数较少,排序较快,随着间隔的减少,分组数的下降,每组记录增多,但是由于各序列已基本有序,所以插入序列仍然比较快。着使得希尔排序比插入排序效率高,。

4、归并排序和冒泡排序这类算法建立在逐个比较的基础上,能够保证稳定性。

 2、选择

(1)数据规模较大,选择平均时间较短的算法,优先考虑快速排序,堆排序,归并排序,树形选择和希尔排序,他们的平均时间夫复杂度为O(nlog_{2}^{n}) 。

           数据规模比较小,选择简单的算法,优先考虑插入排序、交换排序、直接选择排序。

(2)稳定性反映着算法的健壮性,快速排序、希尔排序、堆排序、选择排序不是稳定的排序算法。基数排序、冒泡排序、插入排序、归并排序是稳定排序算法。

(3)初始记录基本有序,可以看最好情况复杂度较小的,可选用直接插入、堆排序、冒泡排序、归并排序、希尔排序。其中插入和冒泡是最快的,时间夫复杂度为O(n)。

        初始记录基本无序,最好使用快速排序、希尔排序、简单选择排序。

(4)空间复杂度过高、排序规模较大时,避免使用归并和树形等空间复杂度较高的算法。

(5)记录所占内存空间比较大时,尽量选择移动次数比较少的算法,如直接选择排序比直接插入排序更合适。

一、插入排序

插入排序就是,从第二个元素开始,依次和前面的比较,如果比前面的元素小,则插入进去。 

基本流程是:

step1:第二个元素是否比第一个元素小?是,则插到第一个元素前面;否,保证位置不变。

step2:第三个元素是否比第二个元素小,是,是否比第一元素小...

依次类推

(1)直接插入排序

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};

//直接插入排序
void InsertSort(LinearList& L) {
	for (int i = 1; i < L.length; i++) {
		//向r[I]中插入r[0:I-1]

		EType t = L.r[i];//保存本次需要插入的元素
		int j;
		for (j = i - 1; j > 0 && t.key < L.r[j].key; j--)//顺次往前比较
			L.r[j + 1] = L.r[j];//数据元素后移
		
		L.r[j + 1] = t;//插入

	}
}

(2)折半排序

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};

//折半插入算法
void BinaryInsert(LinearList& L) {
	EType t;
	int Low, High, m, i, j;

	for (i = 1; i < L.length; i++) {
		t = L.r[i];//保存本次需要插入的元素

		Low = 0; High = i - 1;//设置范围

		while (Low <= High) {
			//若还在插入范围内,则继续插入
			m = (Low + High) / 2;//设置范围中点

			if (t.key < L.r[m].key) {
				//当前点与中点点比较,如果小的话
				High = m - 1;
			}
			else {
				Low = m + 1;
			}
			for (i = i - 1; j >= High + 1; j++)
				L.r[j + 1] = L.r[j];//顺序移动
			L.r[j + 1] = t;
		}
	}
}

(3)希尔排序

希尔排序是,把记录按下标的一定增量分组,对每组使用直接插入排序算法排序;随着增量逐渐减少,每组包含的关键词越来越多,当增量减至1时,整个文件恰被分成一组,算法便终止。

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};

//希尔排序
void ShellSort(LinearList& L, int d[], int number) {
	//用希尔排序法堆记录r[0]-r[n-1]排序,d是增量值数组
	//number是增量值的个数,各组内采用直接插入法排序

	int i, j, k, m, Span;
	EType t;

	for (m = 0; m < number; m++) {
		Span = d[m];
		for (k = 0; k < Span; k++) {
			for (i = k + Span; i < L.length; i++) {
				t = L.r[i];
				j = i - Span;
				while (j > -1 && t.key < L.r[j].key) {
					L.r[j + Span] = L.r[j];
					j -= Span;
				}
			}
    L.r[j + Span] = t;
		}
	}
}

二、交换排序

(1)冒泡排序

 冒泡排序就是,相邻元素相互比较,然后交换顺序

基本流程是:

step1:第一个与第二个比较,第二个与第三个比较

step2:经过step后,相邻元素发生变化了从第二个元素开始比较,第二个与第三个比较,第三个与第四个比较

依次类推

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};

void BublleSort(LinearList& L) {
	int i, j, change;
	change = 1;
	j = L.length - 1;
	while (j > 0 && change) {
		change = 0;
		for (i = 0; i < j; i++) {
			if (L.r[i].key > L.r[i + 1].key) {
				//大于下一个数
				EType temp = L.r[i];
				L.r[i] = L.r[i + 1];
				L.r[i + 1] = temp;
				change = 1;
			}
		}
		j--;
	}
}

(2)快速排序

用数组的第一个数作为关键数据,然后将所有比它小的数都放到它左边,所有比它大的数都放到它右边,这个过程称为一趟快速排序。整个数组找基准正确位置,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面。

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};
//快速排序
int Partition(EType r[], int low, int high) {
	//对记录元素r[low...high]进行一次快速排序,并将本范围的元素按标准元素分为两部分
	//且标准元素在这两部分之间

	KeyType StandardKey;
	EType temp;
	temp = r[low];//标准元素放中间

	StandardKey = r[low].key;//该范围的标准元素
	while (low < high) {
		//从表的两端交替地向中间扫描
		while (low < high && r[high].key >= StandardKey) {
			//大于标准元素
			high--;
			r[low++] = r[high];//小于标准元素的值往前放
		}

		while (low < high && r[low].key >= StandardKey) {
			//大于标准元素
			low++;
			r[high--] = r[low];//大于标准元素的值往后放
		}
	
	}	
	r[low] = temp;//标准元素移到正确位置
	return low;//返回标准位置
}

void QSrot(EType r[], int low, int high) {
	//对记录元素r[low...high]进行快速排序
	int StandardLoc;
	if (low < high - 1) {
		StandardLoc = Partition(r, low, high);
		QSrot(r, low, StandardLoc - 1);
		QSrot(r, StandardLoc - 1,high);
	}
}

void QuickSort(LinearList& L) {
	QSrot(L.r, 0, L.length - 1);
}

三、选择排序

选择排序就是,第一个元素与后面所有元素相比较,

基本流程是:

step1:第一个与第二个比较,第二个与第三个比较

step2:经过step后,相邻元素发生变化了从第二个元素开始比较,第二个与第三个比较,第三个与第四个比较

依次类推

(1)直接选择排序

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};
//直接选择排序算法
void SelectSort(LinearList& L) {
	int i, j, Small;
	EType Temp;
	for (i = 0; i < L.length; i++) {//控制排序的次数
		Small = i;//标记当前查找到的关键字最小的记录在数组中的位置

		for (j = i + 1; j < L.length;j++) {//当前关键字最小的的记录
			if (L.r[j].key < L.r[Small].key) {
				Small = j;
			}
		}
		if (Small != i) {
			Temp = L.r[i];
			L.r[i] = L.r[Small];
			L.r[Small] = Temp;
		}
	}

}

(2)堆排序


#include <iostream>
#include <string>

using namespace std;

typedef char EType;




struct MaxHeap {
	EType* heap;
	int HeapSize;
	int MaxSize;
};

//初始化一个非空的最大堆算法

void MaxHeapInit(MaxHeap& H) {
	//从堆中的数据初始化一个最大堆

	for (int i = H.HeapSize / 2; i >= 1; i--) {
		H.heap[0] = H.heap[i];//将子树根节点的值复制到工作空间heap[0]
		int son = 2 * i;

		while (son <= H.HeapSize) {
			//找到左右孩中较大的节点
			//son <= H.HeapSize时,存在右孩子,如果左孩子小于右孩子,son指向右孩子
			if (son < H.HeapSize && (H.heap[son] < H.heap[son + 1])) {
				son++;
			}
			//大孩子与工作空间的节点值再比较,工作空间的值较大,找到Heap[0]的目标位置
			if (H.heap[0] >= H.heap[son]) break;

			H.heap[son / 2] = H.heap[son];//将大孩子上移动到双亲位子
			son *= 2;//son下移一层到上移的节点(大孩子位置)
		}
		H.heap[son / 2] = H.heap[0];//heap[0]存放的到目标位置
	}
}

bool MaxHeapInsert(MaxHeap& H, EType& x) {
	//插入值为x的节点,到最大堆中,maxsize是数组最大的容量

	if (H.HeapSize == H.MaxSize)
		return false;//数据空间已满
	int i = ++H.HeapSize;//i为插入节点后的节点个数

	while (i != 1 && x > H.heap[i / 2]) {
		H.heap[i] = H.heap[i / 2];

		i = i / 2;//节点下移
	}

	H.heap[i] = x;
	return true;
}


//最大堆中删除堆顶节点,并放入x中算法

bool MaxHeapDelete(MaxHeap& H, EType& x) {

	if (H.HeapSize == 0) return false;
	x = H.heap[1];//最大节点存放到x
	H.heap[0] = H.heap[H.HeapSize--];//最后一个节点放在heap[0],调整堆中元素的个数

	int i = 1, son = 2 * i;

	while (son <= H.HeapSize) {
		//找到左右孩中较大的节点
		//son <= H.HeapSize时,存在右孩子,如果左孩子小于右孩子,son指向右孩子
		if (son < H.HeapSize && (H.heap[son] < H.heap[son + 1])) {
			son++;
		}
		//大孩子与工作空间的节点值再比较,工作空间的值较大,找到Heap[0]的目标位置
		if (H.heap[0] >= H.heap[son]) break;


		H.heap[i] = H.heap[son];//孩子上移
		i = son;		//下移节点指针,继续比较
		son *= 2;//son下移一层到上移的节点(大孩子位置)

	}
	H.heap[i] = H.heap[0];//heap[0]存放的到目标位置
	return true;

}

//堆排序
void HeapSort(MaxHeap& H) {
	//利用堆对H.heap[1:n]数组中的数据排序
	EType x;
	MaxHeapInit(H);

	for (int i = H.HeapSize - 1; i >= 1; i--) {
		MaxHeapDelete(H, x);
		H.heap[i + 1] = x;
	}
}

四、归并排序

归并排序就是,递归得将原始数组递归对半分隔,直到不能再分(只剩下一个元素)后,开始从最小的数组向上归并排序

Step1:向上归并排序的时候,需要一个暂存数组用来排序。

Step2: 将待合并的两个数组,从第一位开始比较,小的放到暂存数组,指针向后移,

Step3:直到一个数组空,这时,不用判断哪个数组空了,直接将两个数组剩下的元素追加到暂存数组里。

Step4:再将暂存数组排序后的元素放到原数组里,两个数组合成一个,这一趟结束。

(1)二路归并排序

#include <iostream>
#include <string>

using namespace std;


#define MAXSIZE 100    //顺序表最大长度
typedef int KeyType;   
typedef int DataType;
struct EType {
	KeyType key;	//关键字项
	DataType elem;
};

struct  LinearList {
	EType r[MAXSIZE + 1];
	int length;
};
//归并排序
bool MergeSort(EType source[], int n) {
	EType* result = new EType[n];

	int size = 1;
	while (size < n) {
		Merge(source, result, size, n);//从a归并到b
		size *= 2;
		Merge(result, source, size, n);//从b归并到a
		size *= 2;
	}
}
//一次二路归并排序
void Merge(EType r[], EType swap[], int size, int n) {
	int i, j, lb1, lb2, ub1, ub2, sp;

	lb1 = 0;//第一个子文件序列的起始位置
	sp = 0;
	while (lb1 + size <= n - 1) {
		//测试是否存在两个可以合并的子文件
		lb2 = lb1 + size;//第二个子文件序列的起始位置
		ub1 = lb2 - 1;//第一个子文件序列的结束位置

		if (lb2 + size - 1 <= n - 1) {//第er个子文件序列的结束位置
			ub2 = n - 1;
		}
		else
			ub2 = lb2 + size - 1;
		for (i = lb1, j = lb2; i <= ub1 && j <= ub2;) {
			if (r[i].key <= r[j].key)
				swap[sp++] = r[i++];
			else
				swap[sp++] = r[j++];
		}
		while (i <= ub1) {
			//序列2已归并完成,将序列1中剩余的记录顺序存放搭配数组swap中
			swap[sp++] = r[i++];
		}
		while (j <= ub2) {
			//序列1已归并完成,将序列2中剩余的记录顺序存放搭配数组swap中
			swap[sp++] = r[j++];
		}
		lb1 = ub2 + 1;
	}
	for (i = lb1; i < n;) {
		//将元素序列中无法配对的子文件顺序存放到数组swap中
		swap[sp++] = r[i++];
	}
}

五、基数排序(桶排序)

       将整数按位数切割成不同的数字,然后按每个位数分别比较。由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。

(1)二维数组桶排序

#include <iostream>
#include <string>

using namespace std;

//基数排序算法实现
#define MAXNUM 100
typedef int KeyType;

struct ETypeR {
	KeyType key;
	
};

void RadixSort1(ETypeR r[],int n,int m) {
	
	int i, j, k, Number, power, h;
	int C[10];//对应桶的计数器
	ETypeR d[10][MAXNUM];
	k = 1; power = 1;
	while (k <= m) {
		if (k == 1) {
			power = 1;
		}
		else {
			power = power * 10;
		}
		for (i = 0; i < 10; i++) {
			C[i] = 0;//清空各桶
		}
		for (i = 0; i < n; i++) {
			//各记录放到相应的桶中
			j = r[i].key / power - (r[i].key / (power * 10)) * 10;
			d[j][C[j]] = r[i];
			C[j] = C[j] + 1;
			Number = 0;
			for (h = 0; h < 10; h++) {
				//从桶中往回收集
				if (C[h] != 0) {
					for (j = 0; j < C[h]; j++) {
						r[Number] = d[h][j];
						Number;
					}
				}
			}
		}
		k++;
	}
}

void main() {
	ETypeR Test[10] = { 712,342,48,686,6,841,429,134,68,264 };
	int length = 10,m = 3;
	int i;
	RadixSort1(Test, length, m);
	cout << "\"" << endl;
	for (i = 0; i < length; i++) {
		cout << Test[i].key << "\"";
	} 
}

(2)链式存储结构桶排序

#include <iostream>
#include <string>

using namespace std;

//桶采用链式存储结构的基数排序
struct ETypeR2 {
	EType data;
	KeyType key;
};

struct radixnode {
	EType data;
	KeyType key;
	radixnode* next;
};
struct radixhead {
	int tub;
	radixnode* link;
}; 

void RadixSort2(ETypeR2 r[], int n, int m) {
	int i, j, k, l, power;
	radixnode* p, *q;

	radixhead head[10];
	for (j = 0; j < 10; j++) {
		//初始化桶结构
		head[j].tub = j;
		head[j].link = NULL;
	}

	power = 1;
	for (i = 0; i < m; i++) {
		//进行m次排序
		if (i == 0)
			power = 1;
		else
			power = power * 10;
		for (l = 0; l < n; l++) {
			q = new radixnode;
			q->data = r[l].data;
			q->key = r[l].key;
			q->next = NULL;
			k=r[l].key/power- (r[l].key / (power * 10)) * 10;
			p = head[k].link;
			if (p == NULL)
				head[k].link = q;
			else {
				while (p->next != NULL)
					p = p->next;
				p->next = q;
			}		
		}
		l = 0;
		for (j = 0; j < 10; j++) {
			p = head[j].link;
			while (p!=NULL) {
				r[l].data = p->data;
				r[l].key = p->key;
				l++;
				q = p->next;
				delete p;
				p = q;
			}
			head[j].link = NULL;
		}
	}
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南叔先生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值