数据结构的常见内部排序

       排序是计算机内经常进行的一种操作,其目的是将一组“无序”的记录序列调整为“有序”的记录序列。分内部排序和外部排序,若整个排序过程不需要访问外存便能完成,则称此类排序问题为内部排序。反之,若参加排序的记录数量很大,整个序列的排序过程不可能在内存中完成,则称此类排序问题为外部排序。内部排序的过程是一个逐步扩大记录的有序序列长度的过程。
       本文将介绍几种常见的内部排序
       以下排序方法通常是建立在顺序表上,也就是我们通常所说的数组

1.插入排序

       1. 直接插入排序

       时间复杂度O(n2)
       算法思想:
              L(0)不存放元素,作为“哨兵”(起监督作用),如下图所示,一个完整       元素数组逻辑上可看做成下面四个板块,哨兵、有序序列、L(i)、无序序列
       在这里插入图片描述
              目的是将元素L(i)插入前面板块的有序序列中,L(1)只有一个元素,故默认有序,然后将第二个元素,也就是L(2)插入到前面的有序板块中,首先将自己赋值给L(0),然后利用哨兵L(0)与前面有序板块的末位向前依次比较大小,如遇到比自己大的元素,则将该大元素向后挪一位,一直向前比较,直到遇到比自己小(算法不稳定)/小于等于(算法稳定)的元素(最差情况是遇到与自己相等的哨兵),然后插入这个元素的前面一位(直接将哨兵的值赋值给其前面一位)
              举例说明:4、6、8、5、3(哨兵L(0)= L(i)= 5)
              (4、6、8 处于有序序列板块,现要将5插入有序板块中;)
              1.哨兵L(0)=5 与8比较,5小于8,8向后挪位,整个序列变为 4、6、8、8、3;
              2.下一步:6与L(0)比较,6比L(0)大,6向后挪位,序列变为4、6、6、8、3;
              3.下一步:4与L(0)比较,4比L(0)小,不挪位,将哨兵插入,第一个6换成L(0),序列变为4、5、6、8、3;
              4.后面的 3 进行插入排序同理
              具体程序实现如下:

//插入排序
void Insertion_sort(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];//直接将哨兵的值赋值给其前面一位
		}
	}
}

       2. 折半插入排序

       时间复杂度O(n2)
       算法思想:主体思想与上文中的直接插入排序相同,区别在于简单插入排序是在有序区逐一与哨兵比较,而折半插入排序是先定位到待插入位置LOCi,然后将有序区LOCi后面的元素向后挪位,最后将哨兵赋值安置在待插入位置Loci;
       注意这里就是为了找到失败位置,即使有序区有与哨兵相同的元素,也不能像之前我们学习的折半查找一样退出(break),而是在high<low时退出
       具体程序实现如下:

//折半插入排序
void Half_insert_sort(int A[],int n){
	int i,j,low,mid,high;
	for (i = 2; i < n; ++i){//从第二个元素开始
			A[0] = A[i];//哨兵就位
			low = 1;
			high = i-1;
			while(low <= high){//开始折半查找插入位置(等号不能丢)
				mid = (low+high)/2;
				if (A[mid]<A[0])				{
					low = mid + 1;
				}else{
					high = mid - 1;
				}
			}
			for (j = i-1; j>high; --j){//准备向后挪位
				A[j+1] = A[j];
			}
			A[high+1]=A[0];//将哨兵赋值安置在待插入位置Loci
		}
}

       3. 希尔排序

       空间复杂度为O(1)、时间复杂度约为O(n1.3)、最坏情况下时间复杂度为O(n2)
       算法思想:隶属于插入排序,将整个排序列表以dk增量序列(此增量序列为认为随机定义,唯一要求是增量序列的最后一个元素是1,如5、3、1或者6、5、3、1或者9、5、3、1都可以)分成dk个组,然后在各个组中分别进行插入排序,注意是插入排序(简单插入排序),不是比较然后交换位置
       常用增量序列(希尔推荐)是dk = n/2、dk=dk/2、、、1
       具体程序实现如下:
       (注意最内部的for循环中 j>0 是必须的,这个是很重要的for循环终止条件)

//希尔排序
void Shell_sort(int A[],int n){
	for (int dk = n/2; dk >= 1 ; dk /=2){//步长变化
		for(int i = dk+1; i<= n; i++){//在每个分组内部进行插入排序
			if (A[i]<A[i-dk]){//递增排序
				A[0] = A[i];//哨兵
				int j;
				for (j = i-dk; j>0 && A[0]<A[j]; j-=dk){//准备向后挪位
					A[j+dk] = A[j];
				}
				A[j+dk]=A[0];
			}
		}
	}
}

2.交换排序

       1. 冒泡排序

       时间复杂度O(n2)
       算法思想:冒泡排序比较简单,也见得比较多,也就是在原n个两两比较,然后一直将大的元素往后排(两两交换位置),直到最后,实现最大(最小)元素浮出,也就是冒泡过程,接下来在剩下的n-1个元素中同样进行此操作。
       具体程序实现如下:

//冒泡排序
void Bubble_sort(int A[],int n){
	int temp;
	for(int i = 1;i<n;i++){
		bool flag = false;//用来判断是否发生交换的标志(也可不用)
		for(int j =1 ;j<n-i;j++){
			if (A[j]>A[j+1]){
				flag = true;
				temp = A[j];
				A[j] = A[j+1];
				A[j+1] = temp;
			}
		}
		if (flag == false){//判断标志位
			break;
		}
	}
}

       2. 快速排序

       下文讲解快速排序应该是很清晰了,可查看下文,用python解释的,不会python的也能看明白
        利用python详讲快速排序算法

3.选择排序

       1. 简单选择排序

       时间复杂度O(n2)
       简单选择是稳定的(参考于严蔚敏老师主编的《数据结构》)
       算法思想:每次找到剩余元素的最小元素,然后将每趟最前面元素与该最小元素互换位置,实现最小元素沉底,最小元素一次存放于i=1、2、3…的位置
       外层循环实现驱动,定位于每趟的最前面元素;内层循环致力于寻找每趟剩余元素中的最小元素的坐标;然后回到外层循环,将每趟最前面元素与该最小元素互换位置。
       具体程序实现如下:

//选择排序
void Select_sort(int A[],int n){
	int tempElement;
	for(int i = 1;i<n;i++){
		int tempkey = i;//从i开始,存放最小元素的下标
		for(int j=i+1;j<n;j++){//定位最小元素的下标
			if (A[j]<A[tempkey]){
				tempkey = j;
			}
		}
		tempElement = A[i];
		A[i] = A[tempkey];
		A[tempkey] = tempElement;
	}
}

       2. 堆排序

       参考博文:堆排序算法(图解详细流程)

4.归并排序

       算法思想:与链表的归并排序一样的原理。
       区别:在于顺序表用的是递归的方法,链表中用的是循环。
       相同:声明备用空数组(链表中的是空链表),然后每次归并比较,将大的数放入备用数组,最后将备用数组中的值复制进入源数组,递归实现的时候,能实现分块存入不同的地方。

int A[] = {0,8,4,5,2,9,6,7,10,58,94,98};//A[0]是哨兵,不存放元素
display(A,12);
int B[12] = {0};
Merge_sort(A,1,11,B);//归并排序
display(A,12);
//归并排序
void Merge_sort(int A[],int low,int high,int B[]){
	if(low < high){
		int mid = (low+high)/2;
		Merge_sort(A,low,mid,B);//分块
		Merge_sort(A,mid+1,high,B);//分块
		Merge(A,low,mid,high,B);//归并
	}
	return;
}

void Merge(int A[],int low,int mid,int high,int B[]){
	int begin_first = low,begin_second = mid+1,i=low;
	//归并比较,然后将大的数插入备份数组B
	while(begin_first<=mid && begin_second<=high){
		if (A[begin_first]<A[begin_second]){
			B[i++] = A[begin_first++];
		}else{
			B[i++] =A[begin_second++];
		}
	}

	//如果两段小序列中的任何一序列未排完
	while(begin_first<=mid){
		B[i++] = A[begin_first++];
	}

	while(begin_second<=high){
		B[i++] = A[begin_second++];
	}
	//printf("B: ");①
	//display(B,i);②
	//将备份数组B排序元素还原到A数组
	for (i = low; i <= high; ++i){
		A[i] = B[i];
	}
}

       每次备份数组中的元素复制进入源数组时,存入的下标为(我这里下标0不存放元素):
1、2
1、2、3
1、2、3(4、5)
1、2、3、4、5

举例:如果我打开上面代码中的①②的注释,结果如下:
在这里插入图片描述

5.基数排序

       算法思想:逐层剥离(个位、十位、百位…),然后根据每次剥离的数放到不同的队列中(此处用的是一个10维数组,此处命名为桶),然后按队列(先进先出,因此稳定)读出,这串序列中最高位有多少位,就循环多少次,就可以逐个分离排序

//基数排序
void Radix_sort(int A[],int n){
	int count[10];//声明一个计数数组,存储当前桶有多少个元素,便于按队列存储
	int digits = getdigits(A,n),base;
	int barrel[10][n];//声明一个二位桶数组
	for (int i=1,multiple=1;i<=digits;++i,multiple*=10){//根据最高位有digits位,循环digits次
		//count桶计数归零
		for (int i = 0; i < 10; ++i){
			count[i] = 0;
		}
		//barrel桶归零
		for (int i = 0; i < 10; ++i){
			for (int j = 0; j < n; ++j){
				barrel[i][j] = 0;
			}
		}
		//将每个元素分装各个桶
		for (int i = 1; i < n; ++i){
			int temp = A[i]/multiple;
			int bottom = temp%10;
			count[bottom]++;//bottom桶计数加一
			barrel[bottom][count[bottom]-1] = A[i];
		}
		//将元素从各个桶中取出来放到A数组中
		base = 1;
		for (int j = 0; j < 10; ++j){
			if (count[j]>0){
                for(int v=0;v<count[j];v++){
                    A[base++] = barrel[j][v];
                }
			}
		}
	}
}

int getdigits(int A[],int n){//取得最大位数
	int maxdigits = 0,tempvalue,count;
	for (int i=1;i<=n;++i){
		tempvalue = A[i];
		count = 1;
		if (tempvalue/10!=0){
			count++;
			tempvalue = A[i]/10;
		}
		while(tempvalue/10!=0){
			count++;
			tempvalue = tempvalue/10;
		}
		if (maxdigits < count){
			maxdigits = count;
		}
	}
	return maxdigits;
}

最后,给出各个排序的性能比较,希望能有所收获~
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值