八大排序 (思想+代码+时间、空间、稳定性分析)

目录

1.冒泡排序

2.插入排序

3.希尔排序

4.快速排序

4.1.hoare法

4.2.挖坑法

4.3左右指针法

快排的优化

1.三数取中

5.归并排序


1.冒泡排序

思想:

1.遍历一遍,将当前区间最大的数字交换到最后一位,注意:假如有n个值需要排序,那么只需要交换n-1次、

2.总共进行(n-1)次第一步操作,每次区间范围都减小一位例如第一次区间[0,n-1]  第二次区间[0,n-2]

代码:

void BubbleSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		//每次循环找到最大的值交换到最后一位
		for (int j = 0; j < n - 1 - i; j++)
		{
			if (a[j] > a[j + 1])
			{
				//交换
				Swap(&a[j],&a[j + 1]);
			}
		}
	}
}

2.插入排序

思想:

1.假如[0,i]此区间是有序的,首先用 tmp 将 a[i+1] 的值保存下来,然后将tmp依次与a[i]、a[i-1]、a[i-2]......a[0]比较,当遇到比 tmp 大的数字(此时对应的下标记为b)并且没有越界的情况下,将 a[b] 对应的数字存放在 a[b+1] 处,继续向下依次比较。只要遇到了比 tmp 小的数字(此时对应的下标记为b),就直接将 tmp 插入在 a[b] 就行了。此时[0,i+1]的区间就是有序的了

2.怎么保证最开始的[0,i]区间有序呢?很简单,只需要最开始将i设为0就一定有序了。

代码:


void InsertSort(int* a, int n)
{
	//最后一次是要将a[n-1]排序,所以最后一次排序时,排好序的区间为[0,n-2]
	for (int i = 0; i < n-1; i++)
	{
		//[0,end]的区间一定是有序的,
		int end = i;
		//保存将要排序的值
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] < tmp)
			{
				//找到临界点,退出循环
				a[end + 1] = tmp;
				break;
			}
			a[end + 1] = a[end];
			end--;
		}
		//tmp是最小的情况上面循环不能判断
		a[end + 1] = tmp;
	}
}

3.希尔排序

思想:希尔排序本质上来说就是对插入排序做了一定的优化

1.希尔排序较之插入排序本质上来说就是多了一个预排序

2,预排序:int gap = 3; 将一串要排序的数字分为三组,相邻(gap-1)个数据的数据在同一组

例如:1,3,5,7,9,2,4,6,8; 第一组:1,7,4。第二组:3,9,6。第三组5,2,8。 最后每一组都进行一次插入排序。

3.gap的值是不确定的,但只要我们能让他最后等于1,就能进行一个标准的插入排序,此时数据就肯定能排好序了。目前普遍有两种gap值的取值方法。 1:gap = gap / 2;         2:gap = gap / 3 +1;

代码:

void ShellSort(int* a, int n)
{
	int gap = n/2;
	while (gap >= 1)
	{
		for (int i = gap; i < n - gap; i+=gap)
		{
			int end = i;
			//保存a[end + gap]的值
			int tmp = a[end + gap];
			while (end < n - gap)
			{
				if (a[end] < tmp)
				{
					//找到临界点,退出循环
					a[end + gap] = tmp;
					break;
				}
				a[end + gap] = a[end];
				end -= gap;
			}
			a[end + gap] = a[end];
		}
		gap /= 2;
	}
}

4.快速排序


4.1.hoare法

思路:1.定义一个 keyi 变量(一般都初始化为0),L、R变量用来记录遍历的下标。

2.先让R走,当遇到比a[keyi]小的值就停下来。再让L走,遇到比a[keyi]大的值就停下来。然后交换L,R里面的值。此次循环就完成了。

3.当L>=R时循环结束,再将a[keyi]的值修改为LR相遇的值。此时代表第一轮遍历结束,能保证a[keyi]左边的值都不大于a[keyi],a[keyi]右边的值都不小于a[keyi].

4.此时采取递归的思路,类似二叉树的先序遍历。遍历a[keyi]左边的数组,再遍历a[keyi]右边的数组。当一开始的L、R的值为L>=R时就return返回此次递归。

代码:

int PartSort1(int* a, int left, int right)
{
	//定义keyi
	int keyi = left;
	while (left < right)
	{
		//右边先走,遇到比a[key]小或等于的就停下来
		while (a[right] >= a[keyi] && left < right)
		{
			right--;
		}
		//左边走,遇到比a[key]大或等于的就停下来
		while (a[left] <= a[keyi] && left < right)
		{
			left++;
		}
		//交换
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	//调整key
	keyi = left;
	return keyi;
}



void QuickSort(int* a, int left,int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort1(a, left, right);
	//[left,keyi-1] keyi [keyi+1,right]
	//递归左边
	QuickSort(a, left, keyi - 1);
	//递归右边
	QuickSort(a, keyi + 1, right);
	return;
}

4.2.挖坑法

思路:

1.定义一个坑位pit,假设取最左边的数组下标,用key保留初始坑位里面的值..定义L,和R记录遍历的下标

2.让R先走,遇到比key小或等的值停下,将R对应的值放在坑位中,再将坑位移动到R。然后让L走,遇到比key大或等的值停下,将L对应的值放在坑位中,再将坑位移动到L。此次循环结束。

3.当L>=R时循环结束,将key的值放入坑位中。

4.此时采取递归的思路,遍历key左边的数组,再遍key]右边的数组。当一开始的L、R的值为L>=R时就return返回此次递归。

代码:

int PartSort2(int* a, int left, int right)
{
	//定义坑位
	int pit = left;
	//保存坑位的初始值
	int key = a[pit];
	while (left < right)
	{
		//右边先走,遇到比a[key]小的就停下来
		while (a[right] >= key && left < right)
		{
			right--;
		}
		//换坑位
		a[pit] = a[right];
		pit = right;
		//左边走,遇到比a[key]大的就停下来
		while (a[left] <= key && left < right)
		{
			left++;
		}
		//换坑位
		a[pit] = a[left];
		pit = left;
	}
	a[pit] = key;
	return pit;
}

void QuickSort(int* a, int left,int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort2(a, left, right);
	//[left,keyi-1] keyi [keyi+1,right]
	//递归左边
	QuickSort(a, left, keyi - 1);
	//递归右边
	QuickSort(a, keyi + 1, right);
	return;
}

4.3左右指针法

思路:

1.定义一个 keyi 变量(一般都初始化为0),左指针prev,右指针cur。

2.让cur往下遍历,当遇到比key小的值时,让prev++,交换prev与cur对应得值。当cur遍历完数组时,则此次循环结束,将key的值与prev的值交换,返回prev对应的下标。

3.此时采取递归的思路,遍历prev]左边的数组,再遍历prev右边的数组。当一开始的prev与cur的值为prev>=cur时就return返回此次递归。

代码:

int PartSort3(int* a, int left, int right)
{
	int prev = left, cur = prev + 1;
	int key = a[prev];
	while (cur <= right)
	{
		if (a[cur] < key )
		{
			prev++;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[left], &a[prev]);
	return prev;
}
void QuickSort(int* a, int left,int right)
{
	if (left >= right)
	{
		return;
	}

	int keyi = PartSort3(a, left, right);
	//[left,keyi-1] keyi [keyi+1,right]
	//递归左边
	QuickSort(a, left, keyi - 1);
	//递归右边
	QuickSort(a, keyi + 1, right);
	return;
}

快排的优化

1.三数取中

.在hoare法和左右指针法中a[keyi]的值有可能是最大的和最小的,那么此时递归的深度就要增加,时间就会增加,要想使递归的深度变小,那么就需要越趋近于把一个数组按二分之一的比例分开递归,那么最好的解决方法就是把a[keyi]里的值改一下,避免a[keyi]的值太大或者太小,这里就用到了三数取中。

代码

//三数取中
int GetMini(int* a, int left, int right)
{
	int tmp = (left + right) / 2;
	if (a[left] > a[right])
	{
		if (a[right] > a[tmp])
		{
			return right;
		}
		// tmp > right
		else if (a[left] > a[tmp])
		{
			return tmp;
		}
		else
		{
			return left;
		}
	}
	else
	{
		if (a[left] > a[tmp])
		{
			return left;
		}
		// tmp > left
		else if (a[right] > a[tmp])
		{
			return tmp;
		}
		else
		{
			return right;
		}
	}
}

//hoare法
int PartSort1(int* a, int left, int right)
{
	//定义keyi
	int keyi = left;
	Swap(&a[a, left, right], &a[left]);
	while (left < right)
	{
		//右边先走,遇到比a[key]小的就停下来
		while (a[right] >= a[keyi] && left < right)
		{
			right--;
		}
		//左边走,遇到比a[key]大的就停下来
		while (a[left] <= a[keyi] && left < right)
		{
			left++;
		}
		//交换
		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	//调整key
	keyi = left;
	return keyi;
}

//左右指针法
int PartSort3(int* a, int left, int right)
{
	int prev = left, cur = prev + 1;
	Swap(&a[GetMini(a, left, right)], &a[left]);
	int key = a[prev];
	while (cur <= right)
	{
		if (a[cur] < key)
		{
			prev++;
			Swap(&a[prev], &a[cur]);
		}
		cur++;
	}
	Swap(&a[left], &a[prev]);
	return prev;
}

5.归并排序

思路:

1.首先需要malloc申请一个跟原数组大小相同的tmp数组。

2.利用递归的思路将数组像二叉树一样分为两半。跟二叉树的先序遍历类似,

当第三次递归结束,第4次递归开始时,a,b数组分别是有序的,此时将a,b数组归并到tmp数组上。

3.归并:定义L表示a数组初始下标,R表示b数组初始下标。将L、R中小的尾插到tmp数组中,若L对应的小,则L++,反之R++。a,b中所有元素都遍历完之后则归并结束。

代码:

void _MergeSort(int* a, int* tmp, int left, int right)
{
	//递归结束条件
	if (left >= right)
	{
		return;
	}

	//定义mid用来将数组分为两半递归
	int mid = (left + right) / 2;
	// [left,mid] [mid+1,right]
	_MergeSort(a, tmp, left, mid);
	_MergeSort(a, tmp, mid + 1, right);

	//归并到tmp数组上,再将tmp数组拷贝回原数组 
	//[left,mid]  [mid+1,right]
	int left1 = left, right1 = mid;
	int left2 = mid + 1, right2 = right;
	//遍历tmp数组的下标
	int add = left1;
	while (left1 <= right1 && left2 <= right2)
	{
		if (a[left1] < a[left2])
		{
			tmp[add++] = a[left1++];
		}
		else
		{
			tmp[add++] = a[left2++];
		}
	}

	//只有一个数组遍历完的情况
	while (left1 <= right1)
	{
		tmp[add++] = a[left1++];
	}
	while (left2 <= right2)
	{
		tmp[add++] = a[left2++];
	}
	//特别注意这里是a+left,tmp+left
	memcpy(a+left, tmp+left, sizeof(int) * (right - left + 1));

	return;
}

void MergeSort(int* a, int n)
{
	int tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("mallon file:");
		exit(-1);
	}
	_MergeSort(a, tmp, 0, n-1);
	free(tmp);
	return;
}

猜你喜欢

转载自blog.csdn.net/qq_73955920/article/details/133610910
今日推荐