交换排序-冒泡+快速排序(无优化+有优化+非递归)

一.交换排序-冒泡排序

 思想:n个数进行排序,进行n-1趟排序,每一趟将相邻的两个元素进行两两比较,将最大的元素排到末尾。没拍完一趟,代排元素的个数-1。

void BubbleSort(int *arr,int n)
{
	for(int j = 1;j <= n-1; j++)//n个元素排n-1趟,
	{
		for (int i = 1; i <= n - j; i++)//第j趟进行两两比较,比较n-j次
		{
			if (arr[i - 1] > arr[i])
			{
				swap(&arr[i - 1], &arr[i]);
			}
		}
	}
}

时间复杂度 :比较次数+交换次数

比较次数:n-1趟总的比较次数,n-1+n-2+n-3+...+1=(n^2-n)/2

交换次数:最坏情况下,即数组完全逆序,每次比较都会有交换,故交换次数也为(n^2-n)/2

故最坏情况下时间复杂度为:O(n^2-n)=O(N^2)

交换次数:最好情况下,数组为顺序,每一趟的每一次比较都不需要交换,故交换次数为0

因此最好情况下的时间复杂度为:O(n^2-n)/2=O(N^2)

优化后的冒泡排序:添加标志位,在第j趟排序加入标志位,如果标志位变动,则说明仍有元素进行交换。如果标志位不变,说明元素数据没交换,则可提前结束。

void BubbleSort(int *arr,int n)
{
	for(int j = 1;j <= n-1; j++)//n个元素排n-1趟,
	{
		int flag = 0;
		for (int i = 1; i <= n - j; i++)//第j趟进行两两比较,比较n-j次
		{
			if (arr[i - 1] > arr[i])
			{
				swap(&arr[i - 1], &arr[i]);
				flag = 1;
			}
		}
		if (flag == 0)//交换未发生,数组元素有序,提前结束
			break;
	}
}

 最坏情况下的时间复杂度:比较次数:每趟比较次数之和(n^2-n)/2,每次比较都交换,交换次数:(n^2-n)/2。时间复杂度为O(n^2-n)=O(N^2)。

最好情况下的时间复杂度:比较次数n-1趟(仅第一次比较),交换次数,0次交换。时间复杂度为O(n-1)=O(n)。

二.交换排序-快速排序 

hoare版本(无优化):选定一个基准值,定义两个左右指针,将该序列分成两个子序列,左子序列的所有值小于=key,右子序列的所有值大于key。

过程:key为当前数组的第一个元素,left是当前数组最左边元素的下标,right是当前数组左右边元素的下标,如果left对应的值小于=key对应的值,则left向右移动,直到找到比key对应元素大=的位置下标,right向左移动,找到比key小的元素下标right。接着交换left,right对应的元素,直到left=right。此时除了left=right的下标对应的元素和key下标对应的元素外,其余数组中左区间的元素都<=key对应的元素,右区间的元素都>=key对应的元素(相等的不交换,不必要)现在交换left=right的下标对应的元素(此元素一定要小于key对应的值!!!)与key下标对应的元素,则左区间的元素全部小于key对应的值,右区间的元素全部大于key对应的值。

(此元素一定要小于key对应的值!!!),否则就会遇到这种情况:

 那如何确保此元素一定要小于key对应的值?

让right遇left,因为left对应的值一定小于key对应的值,这样,当left不动,right先动,left=right对应的元素与key对应的元素交换,一定是小值与key对应的元素交换。

将这段代码
while (arr[left] <= arr[key]&&left<right)
	left++;
while (arr[right] >= arr[key]&&left<right)
	right--;

换成这段代码
while (arr[right] >= arr[key]&&left<right)
	right--;
while (arr[left] <= arr[key]&&left<right)
	left++;

递归什么时候结束?

区间left=right or left>right

void QuickSort(int *arr, int left, int right)
{
	if (left >= right)//将大问题拆解为小问题,小到无需处理时终止
		return;
	int key = left;
	int begin = left;//要重定义i和j作为指针,防止后续递归时无法正确划分区间,如QuickSort(arr, left , key-1); 
	int end = right;
	while (begin < end)//left>=right就结束了
	{
		while (arr[end] >= arr[key] && begin < end)
			end--;
		while (arr[begin] <=arr[key]&& begin < end)
			begin++;
		
		swap(&arr[begin], &arr[end]);
	}
	swap(&arr[key] ,&arr[begin]);//直接在原始数组上操作
	//中间的值固定,左右两个子区间继续快排
	//[left,key-1] key [key+1,right]
	key = begin;
	QuickSort(arr, left , key-1); 
	QuickSort(arr, key+1, right);
}

换个说法就是:如果继续递归递归-left<right,反之递归结束,left>=right。

时间复杂度 :高度×每一层的操作次数(每一层都要遍历元素)

最优情况下时间复杂度:O(n) × O(log₂n) = O(n log₂n)。每次正好能将数组二分

最坏情况下时间复杂度:O(n) × O(n) = O(n²)。划分不均匀,每次key多对应最小的元素

优化版本:

1. 三数取中法选key

使key为数组中居中的元素(大差不差),保证每次都能将数组二分,不让其退化为n^2

思想:将数组第一个元素下标对应的数 与 数组最后一个元素下表对应的数 与  这两个下标/2对应的元素 作比较,找出三个数中位处中间的数,将此数与最左边的元素交换,也就是key对应的数,保证能将数组二分。

int mid(int *arr, int left,int right)
{
	int mid = (left + right) / 2;
	if (arr[mid] < arr[left])//mid left
	{
		if (arr[mid] > arr[right])
			return mid;
		//接下来left right 比较
		else if (arr[left] < arr[right])
			return left;
		else
			return right;
	}
	else {//left mid
		if (arr[mid] < arr[right])
			return mid;
		//接下来left right 比较
		if (arr[right] > arr[left])
			return right;
		else
			return left;
	}
}


void QuickSort1(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//取居中的元素下标与key交换
	int midder = mid(arr, left, right);
	swap(&arr[left], &arr[midder]);

	int key = left;
	int begin = left;
	int end = right;
	while (begin < end)
	{
		while (arr[end] >= arr[key] && begin < end)
			end--;
		while (arr[begin] <= arr[key] && begin < end)
			begin++;

		swap(&arr[begin], &arr[end]);
	}
	swap(&arr[key], &arr[begin]);
	//[left,key-1] key [key+1,right]
	key = begin;
	QuickSort(arr, left, key - 1);
	QuickSort(arr, key + 1, right);
}

时间复杂度:O(n log₂n)

2.小区间优化 

递归开销占比过高,每一层递归都需要保存栈帧、进行参数传递和返回操作。当区间规模很小时(n=10),单次递归的计算量很小,但递归本身的固定开销成为主要耗时,导致 “得不偿失”。

而且还要多次的划分区间,分区操作的准备工作与实际排序的工作量占比失衡。

递归到小的子区间时,可考虑不用递归,用插入排序替代

//直接插入排序
void InsertSort(int* arr, int n)
{
	//end要变+[0,end+1]要变
	//[0,end]有序,end+1为待排的元素
	for (int i = 0; i <= n - 2; i++)
	{
		int end = i;
		int temp = arr[end + 1];//待排序的元素
		//找到合适的位置
		while (end >= 0)
		{
			if (temp < arr[end])//下标为end包括之后的元素全部向后移动覆盖
			{
				arr[end+1] = arr[end];
				end--;
			}
			else { 
				break;
			}
		}
		//temp>=arr[end]
		arr[end + 1] = temp;
	}
}

当区间元素个数<10时,采用直接插入排序

void QuickSort2(int* arr, int left, int right)
{
	if (left >= right)
		return;
	//小区间优化
	if ((right - left + 1) < 10)
		InsertSort(arr, right - left + 1);

	else{
		//取居中的元素下标与key交换
		int midder = mid(arr, left, right);
		swap(&arr[left], &arr[midder]);

		int key = left;
		int begin = left;
		int end = right;
		while (begin < end)
		{
			while (arr[end] >= arr[key] && begin < end)
				end--;
			while (arr[begin] <= arr[key] && begin < end)
				begin++;

			swap(&arr[begin], &arr[end]);
		}
		swap(&arr[key], &arr[begin]);
		//[left,key-1] key [key+1,right]
		key = begin;
		QuickSort(arr, left, key - 1);
		QuickSort(arr, key + 1, right);
	}
}

快速排序-前后指针版本

1.prev指向的值永远<关键字key,

因为:最开始时,prev在最左边,prev根据cur来走

如果cur指向的值>key指向的值,cur++,prev不变(prev仍然是小值)

如果cur指向的值<=key指向的值,prev++,(此时prev指向的仍然是小值),再交换cur与prev指向的值,然后cur++

2.prev和cur之间的值都是比关键字key对应的值大的元素

因为:一旦cur>key,cur就会++,而prev不动

3.prev+1是cur,或者是比关键字key对应的值大的值,这样才能实现左边比key小,右边比key大

因为:prev走的步数是永远超不过cur的,cur每次都++,prev只有在cur<key时才++

除了prev+1=cur的时候,prev++指向的元素都大于关键字key对应的值,因为只有cur指向的值>关

键字key指向的值时,cur与prev才会拉开真正的差距,当所有元素都小于关键字key对应的值时,

prev++,cur++.当有元素大于关键字key对应的值时,prev不动,cur++。prev指向的值时是小值。

 

//前后指针,单趟
int PartSort2(int* a, int left, int right)
{
	//三数取中
	int midder = mid(a, left, right);
	swap(&a[midder], &a[left]);
	int key = left;

	int prev = key;
	int cur = prev + 1;
	while (cur <= right)
	{
		//1.
		/*while (a[cur] > a[key])
		{
			cur++;
		}
		while (a[cur] <= a[key])
		{
			prev++;
			swap(&a[prev], &a[cur]);
			cur++;
		}*/

		//2.
		/*if (a[cur] <= a[key])
		{
			prev++;
			swap(&a[prev], &a[cur]);
			cur++;
		}
		else {
			cur++;
		}*/

		if (a[cur] <= a[key] && ++prev != cur)//自身与自身不交换,prev已经++

			swap(&a[prev], &a[cur]);
		cur++;
	}
	swap(&a[prev], &a[key]);
	return prev;
}

	void QuickSort3(int* arr, int left, int right)
	{

		if (left >= right)
			return;

		int key = PartSort2(arr, left, right);
		QuickSort3(arr, left, key - 1);
		QuickSort3(arr, key + 1, right);
	}
int main()
{
	int arr[] = { 6,1 ,2,7,9,3,4,5,10,8 };
	QuickSort3(arr, 0, sizeof(arr) / sizeof(int) - 1);
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
		printf("%d ", arr[i]);
	return 0;
}

 

 

快速排序-非递归版本

借助栈来实现(深度,一边处理完了再处理另一边)

取栈顶空间,进行单趟排序,左右子区间入栈

先将区间[left,right]的右区间的值right入栈,再将左区间的值left入栈。那取出的顺序就是left,right。

不能用队列,因为先存入的先出队,就没有类似于递归的那种调用操作,队列类似层序(一层一层走)

如果区间内只有一个值或者没有值,就不入栈,不用排序了

 循环每走1次,就相当于递归1次

//快速排序非递归
void QuickSortNo(int* a, int left, int right)
{
	Stack st;
	STInit(&st);
	STPush(&st, right);//先入右再入左
	STPush(&st, left);

	while (!STEmpty(&st))
	{
		int begin= STTop(&st);//取出left
		STPop(&st);
		int end= STTop(&st);
		STPop(&st);
		int key = PartSort2(a, begin, end);

		//右区间入栈
		if(key+1 < end)//先入右,相等表示只有一个值,小于表示至少还有两个值
		{
			STPush(&st, end);//右
			STPush(&st, key + 1);
		}

		//左区间入栈,符合条件才入栈
		if (begin < key-1)
		{
			STPush(&st, key - 1);//右
			STPush(&st, begin);
		}
	}
	STDestory(&st);
}

数据结构的栈相当于递归中调用的栈帧(存在于栈中),但是数据结构的栈不会溢出,存在于堆中,堆>>栈,不用考虑栈太深溢出。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值