【内排序】几大排序:插入排序、交换排序、选择排序、归并排序、基数排序的排序方法

一.插入排序

1.直接插入排序

方法
按给出的一组待排序的记录的关键字序列顺序,依次插入前面已经排序好的序列中。一开始先定义第一个关键字为已排序好的序列,插入时从后往前比较。

步骤
1.将r[i]暂存在临时变量temp中。(假设待排序表中有n个记录,存放在数组中)
2.将temp与r[j] (j=i-1,i-2,…,0)依次比较,若temp.key<r[j].key,则将r[j]后移一个位,直到temp.key≧r[j].key为止(此时j+1即为r[i]的插入位置)
3.将temp插入到第j+1个位置上
4.令i=1,2,3,…,n-1,重复上述步骤

不带监视哨

public void insertSort(){
	RecordNode temp;
	int i,j;
	for(i = 1;i<this.curlen;i++){
		temp = r[i];
		for(j  =i-1;j>= 0 && temp.key.compareTo(r[j].key)<0;j--){
			r[j+1] = r[j];
		}
		r[j+1] = temp;
	}
}

当比较第0个位置时,由于“r[0].key==r[i].key”必然成立,所以会推出循环,因此要加上一个循环条件“temp.key.compareTo(r[j].key)<0”。

带监视哨

public void insertSortWithGuard(){
	int i,j;
	for(i = 1;i<this.curlen;i++){
		r[0] = r[i];
		for(j = i-1;r[0].key.compareTo(r[j].key)<0;j--){
			r[j+1] = r[j];
		}
		r[j+1] = r[0];
	}
}

算法性能分析

1.总的比较次数
n-1i=1i=1/2n(n-1)
2.总的移动次数
n-1i=1(i+2) = 1/2n(n-1)+2n
3.空间复杂度
O(1)
4.平均时间复杂度
O(n2)
5.稳定性
稳定

2.希尔排序

方法
先选取一个小于n的整数di作为增量,然后把排序表中的n个记录分为di个子表,从下标为0开始间隔为di的记录组成一个子表(也就是有d+1个数),然后把第0个数与第d个数进行比较,若第0个数比第d个数大,则交换位置,重复上述步骤,直到把所有子表都比较完后,逐步减小增量di,再重复上述步骤,直到di=1,使得间隔为1的记录有序,也就是整个序列都达到有序。

步骤
1.选择一个增量序列{d0,d1,…,dk-1}
2.根据当前增量di将n条记录分为di个子表,每个子表中记录的下标间隔为di
3.对各个子表中的记录进行直接插入排序
4.令i=0,1,2,…,k-1,重复上述步骤

public void shellSort(int[] d){
	RecordNode temp;
	int i,j;
	for(int k = 0;k<d.length;k++){
		int dk = d[k];
		for(i = dk;i<this.curlen;i++){
			temp = r[i];
			for(j = i-dk;j>=0 && temp.key.compareTo(r[j].key)<0;j-=dk){
				r[j + dk] = r[j];
			}
			r[j + dk] = temp;
		}
	}
}

算法性能分析

1.空间复杂度
O(1)
2.时间复杂度
与增量序列选择有关
3.稳定性
不稳定

二.交换排序

1.冒泡排序

方法
第1趟中,从第0个记录开始到第n-1个记录,两两相邻进行比较,若要求升序则最大的关键字则被交换到r[n-1]的位置上;第2趟,则从第0个记录开始到第n-2个记录进行排序,重复上述步骤,进行n-1趟排序。

步骤
1.置初值i=1
2.在无序序列{r[0],r[1],…,r[n-1]}中,从头至尾依次比较相邻的两个记录r[j]与r[j+1] (0≤j≤n-i-1),若r[j].key>r[j+1].key,则交换位置
3.i=i+1
4.重复上述步骤,直到步骤2中未发生交换或i=n-1为止

要实现上述步骤,需要引入一个布尔变量flag,用来标记相邻记录是否发生交换

public void bubbleSort(){
	RecordNode temp;
	boolean flag = true;
	for(int i = 1li<this.curlen && flag;i++){
		flag = false;    //记录未发生交换
		for(int j = 0;j<this.curlen - i;j++){
			if(r[j].key/compareTo(r[j+1].key)>0){  //逆序时交换
				temp = r[j];
				r[j] = r[j+1];
				r[j+1] = temp;
				flag = true;
			}
		}
	}
}

算法性能分析

1.空间复杂度
O(1)
2.平均时间复杂度
O(n2)
3.稳定性
稳定
4.总的比较次数
n-1i=1 (n-i) = 1/2n(n-1)
5.总的移动次数
n-1i=1 3(n-i)=3/2n(n-1)

2.快速排序

方法
选取最左或最右为key,定义一个begin和end,begin从左向右,end相反,选最左为key则end先走。若end遇到小于key则begin开始走,直到遇到大于key值的,此时begin和end交换,重复上述步骤。直到begin和end相遇,把key值放在相遇点,此时key左边为小于它的值,右边为大于它的值,然后两个子表重复上述步骤,直到各子表只有一个数。

步骤
1.设置两个变量i、j,初始值分别为low和high,分别表示待排序序列的起始下标和终止下标
2.将第i个记录暂存在变量pivot中,即pivot=r[i]
3.从下标为j的位置开始由后往前依次搜索,当找到第1个比pivot的关键字值小的记录时,则将该记录向前移动到下标为i的位置上,然后i=i+1
4.从下标为i的位置开始由前向后依次搜索,当找到第1个比pivot的关键字值大的记录时,则将该记录向后移动到下标j的位置上,然后j=j-1
5.重复3、4步,直到i==j为止
6.r[i]=pivot

一趟快速排序算法

//交换排序表r[i..j]的记录,使支点记录到位,并返回其所在位置
public int Partition(int i,int j){
	RecordNode pivot = r[i];
	while(i<j){
		while(i<j && pivot.key.compareTo(r[j].key)<=0){
			j--;
		}
		if(i<j){
			r[i] = r[j];
			i++;
		}
		while(i<j && pivot.key.compareTo(r[i].key)>0){
			i++;
		}
		if(i<j){
			r[j] = r[i];
			j--;
		}
	}
	r[i] = pivot;
	return i;
}

对子表r[low…high]采取递归形式的快速排序算法:

public void qSort(int low,int high){
	if(low<high){
		int pivotloc = Partition(low,high);
		qSort(low,pivotloc - 1);
		qSort(pivotloc + 1,high);
	}
}

顺序表r[0…curlen-1]的快速排序算法:

public void quickSort(){
	qSort(0,this.curlen-1);
}

算法性能分析

1.空间复杂度
O(log2n)
2.平均时间复杂度
O(nlog2n)
3.稳定性
不稳定

三.选择排序

1.直接选择排序

方法
在第1趟中,在n个记录里找最小值,与第1个记录交换。第2趟在n-1个记录里找最小值,与第二个记录交换,重复上述步骤,直到整个序列的关键字值有序为止

步骤
1.置i的初值为0
2.当i<n-1时,重复下列步骤
①在无序子序列{r[i+1],…,r[n-1]}中选出一个关键字最小的记录r[min]
②若r[min]不是r[i] (即min!=i),则交换r[i]和r[min]的位置,否则不进行任何交换
③将i的值加1

public void selectSort(){
	RecordNode temp;
	for(int i = 0;i<this.curlen-1;i++){
		//每趟从r[i]开始的子序列中寻找最小关键字
		int min = i;
		for(int j = i+1;j<this.curlen;j++){
			if(r[j].key.compareTo(r[min].key<0){
				min = j;
			}
		}
		if(min != i){
			temp = r[i];
			r[i] = r[min];
			r[min] = temp;
		}
	}
}

算法性能分析

1.空间复杂度
O(1)
2.平均时间复杂度
O(n2)
3.稳定性
不稳定
4.总的比较次数
最好:0
最坏(逆序):
3(n-1)
5.总的移动次数
n-1i=1 3(n-i)=3/2n(n-1)

2.树形选择排序

方法
针对n个记录,进行两两比较,将关键字值较小者作为优胜者上升到父结点,得到[n/2]个比较的优胜者,作为第一步的结果,然后对这[n/2]个结果重复上述步骤,直到选出一个最小的关键字值为止,所有所有的根节点中的关键字值就是叶子结点中的最小关键字。
选出次小关键字记录时,只需将叶子结点中的最小关键字值改为“∞”

步骤
1.变量初始化,令待排序的结点个数为n,则leafSize=n,TreeSize=2n- 1,loadindex=n-1
2.将n个待排序结点复制到胜者树的n个叶子结点中,即将r[0…n-1]依次赋值到tree[loadindex…TreeSize-1]中
3.构造胜者树:将n个叶子结点的关键字进行两两比较,得到n/2个关键字值较小的结点,保留下来,再将n/2个结点的关键字进行两两比较,得到n/4个较小关键字值的结点,保留下来,依次类推,最后得到根结点为最小关键字值的结点为止。
4.调整优胜树:先将根结点保存到原数组r中,再把具有根结点值所对应的叶子结点的值改为“最大值”,然后从该叶子结点开始,和其左(或右)兄弟的值进行比较,修改从该叶子结点到根的路径上各结点的值,直到根结束。
5.重复步骤4,直到得到n个结点为止

//建立树的顺序存储数组tree,并对其排序,将结果返回到r中
void tournamentSort(){
	TreeNode[] tree;  //胜者树结点数组
	int leafSize = 1;  //胜者树的叶子结点数
	while(leafSize<this.curlen){
		leafSize *= 2;
	}
	int TreeSize = 2 * leafSize - 1; //胜者树的所有结点数
	int loadindex = leafSize - 1;  //叶子结点(外结点)存放的起始位置
	tree = new TreeNode[TreeSize];
	int j = 0;
	//把待排序结点复制到胜者树的叶子结点中
	for(int i = loadindex;i<TreeSize;i++){
		tree[i]=new TreeNode();
		tree[i].index=i;
		if(j<this.curlen){  //复制结点
			tree[i].active = 1;
			tree[i].data = r[j++];
		}else{      
			tree[i].active = 0;    //空的外结点
		}
	}
	int i = loadindex;   //进行初始比较查找关键子值最小的结点
	while(i>0){      //产生优者树
		j = i;
		while(j<2 * i){       //处理各对比赛者
			if(tree[j+1].active == 0 || ((tree[j].data).key.compareTo((tree[j+1].data.key))<=0){
				tree[(j-1)/2 = tree[j];  //左孩子为胜者赋值给父结点
			}else{
				tree[(j-1)/2] = tree[j+1];    //右孩子为胜者赋值给父结点
			}
			j += 2;   //下一对比赛者
		}
		i = (i-1)/2;   //处理上层结点
	}
	for(i = 0;i<this.curlen-1;i++){  //处理剩余的n-1个记录
		r[i].tree[0].data;   //将胜者树的根(最小者)存入数组r
		tree[tree[0].index].active = 0;  //该记录相应外结点不再比赛
		updateTree(tree,tree[0].index);  //调整胜者树
	}
	r[this.curlen - 1] = tree[0].data;
}

调整算法:即从当前最小关键字的叶子结点开始到根结点路径上的所有结点关键字的修改

void updateTree(TreeNode[] tree,int i){
	int jl
	if(i % 2 == 0){
		tree[(i-1)/2] = tree[i-1];  //i为偶数,对手为左结点
	}else{
		tree[(i-1)/2] = tree[i+1];  //i为奇数,对手为右结点
	}
	i = (i-1)/2;   //最小记录输出后,其对手上升到父结点
	while(i>0){   //直到i==0
		if(i % 2 == 0){  //i为偶数,对手为左结点
			j = i-1;
		}else{   //i为奇数,对手为右结点
			j = i+1;
		}
		//比赛对手中有一个为空
		if(tree[i].active == 0 || tree[j].active == 0){
			if(tree[i].active == 1){
				tree[(i-1)/2] = tree[i];   //i可参选,i上升到父结点
			}else{
				tree[(i-1)/2] = tree[j];  //否则,j上升到父结点
			}
		//双方都可参选。关键字值较小者上升到父结点
		}else if((tree[i].data).key.compareTo((tree[j].data).key)<=0){
			tree[(i-1)/2] = tree[i];
		}else{
			tree[(i-1)/2] = tree[j];
		}
		i = (i-1)/2;    //i上升到父结点
	}
}

算法性能分析

1.空间复杂度
树形选择排序虽然减少了排序时间,但使用了较多的存储空间
2.平均时间复杂度
O(nlog2n)
3.稳定性
稳定
4.总的比较次数
O(nlog2n)

3.堆排序

大顶堆:每个结点值都大于其左右孩子结点的值(升序)
小顶堆:每个结点值都小于其左右孩子结点的值(降序)
堆的结构为完全二叉树
方法(小顶堆):
按所给待排序关键字序列顺序,依次建成堆。第1个与第2个关键字值进行比较,小的为父结点,大的为左结点,第3个值放在右结点并与父结点进行比较,若第3个值小于父结点则交换,以此类推。然后将顶端的值与最后一位数交换位置,并把最后一位数固定,剩余值重复上述步骤,将顶端的值与其左右孩子比较。直到剩下一个结点为止。

public void heapSort(){
	int n = this.curlen;
	RecordNode temp;
	for(int i = n/2-1;i>=0;i--){
		sift(i,n);  //筛选法调整堆
	}
	for(int i = n-1;i>0;i--){  //每趟将最小关键字与最后一位数交换,再调整堆
		temp = r[0];
		r[0] = r[i];
		r[i] = temp;
		shift(0,i);
	}
}

算法性能分析

1.空间复杂度
O(1)
2.平均时间复杂度
O(nlog2n)
3.稳定性
不稳定

四.归并排序

方法
首先将待排序的序列不断切分为若干个子表,直到每个子表只有一个关键字。将两个子表进行比较,a子表的第一个元素为i,b子表第一个元素为j,若j比i小,则j元素放在有序序列中,然后j后移一位。进行两两子表合并,直到最后只有一个序列表。
在这里插入图片描述

算法性能分析

1.空间复杂度
O(n)
2.平均时间复杂度
O(nlog2n)
3.稳定性
稳定
4.总的比较次数
O(nlog2n)

五.基数排序

1.多关键字排序

最主位优先MSD法

先按k1排序分组,同一组记录,若关键字k1相等,再对各组按k2排序分成子组,直到按最次位关键字kd对各子表排序后,再将各组连接起来,便得到有序序列。
如对扑克牌排序🃏,先对花色排序,将其分为4组,在对每组分别按面值排序,最后将4个组连接起来。

最次位优先LDS法

先从kd开始排序,再对kd-1进行排序,重复上述步骤,直到对k1排序后,便得到一个有序序列
如对扑克牌排序🃏,先按13个面值排序分成13堆,再对每堆中按花色排序,最后依次连接起来

2.链式基数排序

方法
将每个关键字拆分为若干项
假设一组待排序序列中,最大有三位数(不够三位数的则在前面补0),第1趟,按给出序列的每个关键字的个位数依次放入对应的桶(数字0-9)中,然后按先进先出的顺序,从桶0开始依次连接成链队列。第2趟,则按第1趟的链队列的每个关键字的十位数,重复上述步骤。第3趟也如此,至此完毕。
在这里插入图片描述
不要嫌我字丑哩🙈

步骤
1.形成初始链表作为当前的处理序列
2.将最小的关键字值作为当前关键字,即i=d
3.执行第i趟分配和手机,即改变序列中各个记录的指针,使当前的处理序列按该关键字分成rd个子序列,链头和链尾分别由f[0…rd-1]和e[0…rd-1]指向,再将者rd个子序列头尾相连形成一个新的当前处理序列
4.将当前关键字向高位推进一位,即i=i-1;重复执行步骤3,直至d位关键字都处理完毕

算法性能分析

1.空间复杂度
O(rd)
2.平均时间复杂度
O(d(n+rd))
3.稳定性
稳定


动动你美丽的小手给我点个赞呗😉
写得不好的地方欢迎评论

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值