数据结构-内部排序

目录

一、插入排序

1、直接插入排序

2、折半插入排序

3、希尔排序

二、交换排序

1、冒泡排序

2、快速排序

三、选择排序

1、简单选择排序

2、堆排序

四、归并排序

五、基数排序

六、计数排序

七、比较和应用


一、插入排序

1、直接插入排序

     过程:假设序列:L[1...i-1] L[i] L[i+1...n],前Lq=L[1...i-1]有序,将L[i]与Lq从后往前比较,找到插入位置k,将L[k...i-1]的所有元素依次往后移一个元素,再复制。∴上述操作执行n-1次就能得到一个有序的表。

//直接插入排序
void InsertSort(int A[], int n) {//n个元素,0位不存放元素,哨兵
	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];
		}
}

空间复杂度:O(1)

时间复杂度:在整个过程中,需要进行n-1趟操作。每趟操作都分为:比较和移动。比较和移动次数取决于排序表的初始状态。最好情况:已有序,不用移动,O(n)。 最坏情况:逆序,比较和移动次数都达到最大,O(n^{_{2}})。故平均时间复杂度:O(n^{_{2}})

稳定性:稳定

适用性:适用于顺序存储和链式存储的线性表,采用链式存储时无需移动元素。

2、折半插入排序

     在直接插入排序的基础上,把比较和移动操作分离。即先折半查找出元素的待插入位置,再统一移动待插入位置之后的所有元素

void InsertSort(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;
		//二分 最终high会在low的左边
		while (low <= high) {
			mid = (low + high) / 2;
			if (A[mid] > A[0])high = mid - 1;
			else low = mid + 1;
		}
		//移动元素
		for (j = i - 1; j >= high + 1; j--)
			A[j + 1] = A[j];
		A[high + 1] = A[0];
	}
}

空间复杂度:O(1)

时间复杂度:比较元素次数减少,时间复杂度为:O(nlog_{2}^{}\textrm{}n),比较次数与待排序表的初始状态无关移动元素次数依赖于待排序表的初始状态。平均时间复杂度:O(n^{_{2}})。对于数量不是很大的排序表,折半插入排序能表现出很好的性能。

稳定性:稳定

适用性:适用于顺序存储的线性表

3、希尔排序

     把排序表分割成若干子表:L[i,i+d,i+2d....]为一个子表,对该子表进行直接插入排序,多次排序后,再对全体记录进行一次直接插入排序。

void ShellSort(int A[], int n) {
	int d, i, j;//d为增量
	for (d = n / 2; d >= 1; d /= 2)
		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];
			}
}

空间复杂度:O(1)

时间复杂度:比较元素次数减少,时间复杂度为:O(nlog_{2}^{}\textrm{}n),比较次数与待排序表的初始状态无关移动元素次数依赖于待排序表的初始状态。平均时间复杂度:O(n^{_{2}})。对于数量不是很大的排序表,折半插入排序能表现出很好的性能。

稳定性:不稳定

适用性:适用于顺序存储的线性表

二、交换排序

1、冒泡排序

     两个两个作比较。每一趟冒泡都能确定当前无序表的最小元素的位置。这样最多做n-1趟冒泡就能排好序。如果某趟冒泡结束后没有发生交换,说明已经有序,冒泡排序结束。

void BubbleSort(int A[], int n) {
	for (int i = 0; i <n; i++) {//每一趟冒泡
		bool flag = false;//暂定flag为没有发生交换
		for (int j = n - 1; j > i; j--) {//i后面的数要排序
			if (A[j - 1] > A[j]) {
				swap(A[j], A[j - 1]);
				flag = true;
			}

		}
		if (flag == false)return;//没有发生交换,结束
	}
}

空间复杂度:O(1)

时间复杂度:最好情况:比较次数为n-1,移动次数为0。时间复杂度为:O(n)

                      最坏情况:逆序,要进行n-1趟排序。第i趟要进行n-i次比较。平均时间复杂度:O(n^{_{2}})

稳定性:稳定

适用性:适用于顺序存储和链式存储的线性表

2、快速排序

      基于分治的思想。取定x,通过排序,把所有小于x的数放在x的左边,所有大于等于x的数放在x的右边。而x现在的位置,就是x的最终的位置。再将左右两个子区间进行递归排序。直至每部分内只有一个元素或为空位置。每一趟排序后会将上一趟的基准元素归位。

void QuickSort(int A[], int low, int high) {//传参为下标
	if (low >= high)return; //
	int x = A[low + high >> 1]; //取中间数
	int i = low - 1, j = high + 1;
	while (i < j) {
		do i++; while (A[i] < x);
		do j--; while (A[j] > x);
		if (i < j)swap(A[i], A[j]);
	}
	QuickSort(A, low, i - 1);//对x左边排序
	QuickSort(A, i, high);//对x右边排序
}

空间复杂度:递归,容量与递归调用的最大层数一致。

                     最好情况:O(log_{2}n)。//把n个元素组成二叉树,共h层=递归调用层数

                     最坏情况下,进行n-1次递归:O(n)平均:O(log_{2}n)

时间复杂度:快排的运行时间与划分是否对称有关,最坏情况为两个区域为n-1和0个元素。

                     最好情况:平衡划分。时间复杂度为:O(nlog_{2}^{}\textrm{}n)

                     最坏情况:基本有序或逆序,最坏情况下时间复杂度:O(n^{_{2}})

稳定性:不稳定

适用性:适用于顺序存储的线性表

三、选择排序

1、简单选择排序

    在无序表中,每一趟都把关键字最小的元素与L[i]交换,每一趟可以确定一个元素的最终位置。

void SelectSort(int A[], int n) {
	for (int i = 0; i < n-1; i++) { //最后一个元素不用排序
		int min = i;//把当前i指向的元素设为最小值
		for (int j = i + 1; j < n; j++) //遍历后面的数
			if (A[j] < A[min])min = j;//如果找到了比i更小的数,min指向这个更小的数
		if (min != i)swap(A[i], A[min]);//如果最小数有改变,交换
	}
}

空间复杂度:O(1)

时间复杂度:元素移动次数少,不超过3(n-1)次,最少为0次,此时已有序。但元素的比较次数与序列的初始状态无关,始终是n(n-1)/2次,故时间复杂度:O(n^{_{2}})

稳定性:不稳定

适用性:适用于顺序存储和链式存储的线性表,以及关键字较少的情况

2、堆排序

  将堆视为一颗完全二叉树,分为大根堆和小根堆(注意不是排序二叉树);

思路:1、建堆

           2、根据堆的特点(以大根堆为例),堆顶为最大元素。输出堆顶元素后,通常将堆底元素送入堆顶,堆被破坏。

           3、调整。

建堆+调整+排序:n个结点的完全二叉树,最后一个结点是第\left \lfloor n/2 \right \rfloor个结点的孩子。对以第\left \lfloor n/2 \right \rfloor个结点为根的子树进行筛选,使该树成为堆。再向前依次对以各结点(\left \lfloor n/2 \right \rfloor-1 ~ 1)为根的子树进行筛选。

//建堆
//处理每个非终端节点
void BuildMaxHeap(int A[], int len) {
	for (int i = len / 2; i > 0; i++)
		HeadAdjust(A, i, len);
}

//对以k为根的子树进行调整
void HeadAdjust(int A[], int k, int len) {
	A[0] = A[k];//暂存子树的根节点
	for (int i = 2 * k; i <= len; i*=2) {//i为左孩子,沿关键字较大的子节点向下筛选
		if (i < len && A[i] < A[i + 1]) //如果k有左右孩子结点并且右孩子大于左孩子
			i++;//i移动到右孩子
		if (A[0] >= A[i])break;//如果根节点大于右孩子,则也大于左孩子,不需要调整
		else {//调整
			A[k] = A[i];//将A[i]调整到双亲结点上
			k = i;//继续向下筛选
		}
	}
	A[k] = A[0];//被筛选的结点最终位置
}
//一颗完全二叉树,先从最后一层最后一个子树进行筛选。
// 将最后一层筛选后,再进行倒数第二层的筛选,同时会检查某第二层结点调整后,该结点是否还符合堆的特性,并依次向下筛选

//排序
void HeapSort(int A[], int len) {
	BuildMaxHeap(A, len);//建堆
	for (int i = len; i > 1; i--) {//n-1趟的交换和建堆的过程
		swap(A[i], A[1]);//输出堆顶元素(和堆底交换)
		HeadAdjust(A, 1, i - 1);//调整
	}
}

调整时间和树的高度有关,为O(h)

在建含n个元素的堆时,比较总次数不会超过4n,为O(n)。说明在线性时间内可以建一个堆

一个结点,每下坠一层,最多只需对比关键词2次。若树高为h,某结点在第i层,则将该结点向下调整最多只需要下坠h-i层,关键字对比不超过2(h-i)次。

n个结点的完全二叉树树高h=\left \lfloor log_{2}n \right \rfloor +1,第i层最多有2^{i-1}个结点,而只有第1~(h-1)层的结点可能要下坠。

空间复杂度:O(1)

时间复杂度:建堆时间为O(n),之后有n-1次向下调整操作,调整为O(h)。所以在最好/坏/平均情况下,为O(nlog_{2}^{}\textrm{}n)

稳定性:不稳定

适用性:适用于顺序存储的线性表,以及关键字较多的情况

堆的插入操作:先将新节点放在堆的末端,再对这个结点进行向上操作(向上操作时,只对该结点的路线进行调整,其他子树不需要调整)。(向上调整算法,比较次数最多等于树的高度-1,因为树的高度为\left \lfloor log_{2}n \right \rfloor +1,所以堆的向上调整算法的比较次数最多等于\left \lfloor log_{2}n \right \rfloor)

堆的删除操作:删除结点后用堆底端元素进行代替,再调整。

四、归并排序

将无序表视为n个有序的子表,每个子表长度为1,然后两两归并,得到\left \lceil n/2 \right \rceil个长度为2或1的有序表,再继续两两归并.....直到合成一个长度为n的有序表,这叫二路归并。m路归并:每选出一个元素需要对比关键字m-1次。

int* B = (int*)malloc((n + 1) * sizeof(int));//辅助数组
void MergeSotr(int A[], int l, int r) {
	if (l >= r)return;//递归结束条件
	int mid = l + r >> 1;

	MergeSotr(A, l, mid), MergeSotr(A, mid + 1, r);//递归

	int k = 0, i = l, j = mid + 1;//分为两个有序数组,进行整合

	while (i <= mid && j <= r) {
		if (A[i] <= A[j])B[k++] = A[i++];
		else B[k++] = A[j++];
	}
	while(i<=mid)B[k++] = A[i++];//有一方已经指到最后,剩余的直接放到B里
	while(j<=r)B[k++] = A[j++];

	for (i = l, j = 0; i <= r; i++, j++)A[i] = B[j];//恢复A数组
}

对于N个元素进行k路归并,排序的趟数m满足k^m{}=N,从而m=log_{k}N,考虑到m为整数,因此m=\left \lceil log_{k}N \right \rceil

空间复杂度:O(n)

时间复杂度:每趟归并的时间为O(n),共需要\left \lceil log_{2}n \right \rceil趟归并,所以时间复杂度为O(nlog_{2}^{}\textrm{}n)。

稳定性:稳定

适用性:适用于顺序存储和链式存储的线性表

五、基数排序

借助多关键字排序的思想对单逻辑关键字进行排序。分为最高位优先和最低位优先。按关键字位权重依次逐层划分为若干更小的子序列,最后将所有子序列依次连接成一个有序序列。

空间复杂度:r个队列:r个对头和队尾指针。O(r)

时间复杂度:有d趟分配和收集操作。一趟分配需要遍历所有关键字,O(n)。一趟收集需要合并r个队列,O(r),因此时间复杂度为O(d(r+n))。且它与序列的初始状态无关。

稳定性:稳定

适用性:适用于顺序存储和链式存储的线性表

六、计数排序

七、比较和应用

  

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值