排序算法:冒泡排序、选择排序、插入排序、希尔排序、堆排序、归并排序、快速排序

目录

1、冒泡排序

2、选择排序

3、插入排序

4、希尔排序

5、堆排序

6、归并排序

7、快速排序

7.1 hoare法

7.2 挖坑法

7.3 前后指针

7.4优化思路

a、三数取中

b、插入排序


1、冒泡排序

基本思路:

相邻元素两两比较,满足条件就交换,不满足就直接下一位,每次循环确定一位最大或最小的元素。

动图如下:

代码如下:

void BubbleSort(int* arr, int n)//n是数组长度
{
	for (int end = n - 1; end > 0; end--)
	{
		int flag = 0;//标记单个循环内是否进行了交换
		for (int i = 0; i < end; i++)
		{
			//相邻两元素,前者比后者大,交换(升序)
			if (arr[i] > arr[i + 1])
			{
				int temp = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = temp;
				flag = 1;
			}
			//如果没交换,说明已经有序,直接跳出
			if (flag == 0)
			{
				break;
			}
		}
	}
}

2、选择排序

基本思路:

第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。

动图如下:

代码如下:

void SelectSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int min = i;//记录最小值的下标
		for (int j = i + 1; j < n; j++)
		{
			//如果arr[j]的值比arr[min]小,更新min的值
			if (arr[min] > arr[j])
			{
				min = j;
			}
		}
		//第i个位置的值与其后面元素中的最小值进行交换
		if (i != min)
		{
			int temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}

3、插入排序

基本思路:

和抓扑克牌一个道理,第一个看作有序,其后面的看作无序,抓一张新的和前面的进行比较,找到合适的位置插入,前面有序的就多一个,后面无序的就少一个。以此类推直到无序的为个数为0。

动图如下:

 代码如下:

void InsertSort(int* arr, int n)
{
	for (int i = 0; i < n - 1; ++i)
	{
		// [0,end] 插入 end+1 [0, end+1]有序
		int end = i;
		int tmp = arr[end + 1];
		while (end >= 0)
		{
			if (arr[end] >= tmp)
			{
				arr[end + 1] = arr[end];
				--end;
			}
			else
			{
				break;
			}
		}

		arr[end + 1] = tmp;
	}
}

4、希尔排序

基本思路:

就是进行多次间隔距离为gap(递减)的插入排序,最后直到间隔距离为1的时候就是原原本本的插入排序,先前间隔距离大于一的插入排序称之为预排序。

动图如下:

代码如下:

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap = gap / 2;

		// [0,end] 插入 end+gap [0, end+gap]有序  -- 间隔为gap的数据
		for (int i = 0; i < n - gap; ++i)
		{
			int end = i;
			int tmp = a[end + gap];
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

5、堆排序

堆的性质:

       a、堆中某节点的值总是不大于或者不小于其父节点的值。

       b、堆总是一个完全二叉树。

动图如下:

 代码如下:

a、建堆

       升序建大堆,降序建小堆

void AdjustDown(int *arr, int parent,  int n)
{
	//暂定左孩子为较小的孩子
	int minChild = parent * 2 + 1;

	while (minChild < n)
	{
		//选出较大的那个孩子
		if (minChild < n && arr[minChild + 1] > arr[minChild])
		{
			minChild++;
		}
            
        //小的往下面往下面沉,大的往上浮
		if (arr[parent] < arr[minChild])
		{
			int temp = arr[parent];
			arr[parent] = arr[minChild];
			arr[minChild] = temp;

			parent = minChild;
			minChild = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
	// 升序  建大堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDwon(a, n, i);
	}

b、排序

        在已经建好堆的基础上,每次循环将堆顶的元素和最后一个元素交换,这样最大或者最小的就在尾部,然后将计算的元素个数减一,循环至只剩下一个元素,就已经排好序了。

void HeapSort(int* a, int n)
{
	// 依次选数,从后往前排
	// 升序 -- 大堆
	// 降序 -- 小堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(a, n, i);
	}

	int i = 1;
	while (i < n)
	{
		Swap(&a[0], &a[n - i]);
		AdjustDown(a, n - i, 0);
		++i;
	}
}

6、归并排序

基本思路:

a、将整个待排序序列划分成多个不可再分的子序列,每个子序列中仅有 1 个元素;

b、所有的子序列进行两两合并,合并过程中完成排序操作,最终合并得到的新序列就是有序序列。

动图如下:

代码如下:

void _MergeSort(int* a, int begin, int end, int* tmp)
{
	if (begin >= end)
		return;

	int mid = (end + begin) / 2;
	// [begin, mid] [mid+1, end]

	_MergeSort(a, begin, mid, tmp);
	_MergeSort(a, mid+1, end, tmp);

	// 归并 取小的尾插
	// [begin, mid] [mid+1, end]
	int begin1 = begin, end1 = mid;
	int begin2 = mid+1, end2 = end;
	int i = begin;
	while (begin1 <= end1 && begin2 <= end2)
	{
		if (a[begin1] <= a[begin2])
		{
			tmp[i++] = a[begin1++];
		}
		else
		{
			tmp[i++] = a[begin2++];
		}
	}

	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}

	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}

	// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
	memcpy(a+begin, tmp+begin, (end-begin+1)*sizeof(int));
}

void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
	tmp = NULL;
}

非递归就是模拟递归后半阶段:

需要注意的就是边界的调整:

void MergeSortNonR(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int)*n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}

	int gap = 1;
	while (gap < n)
	{
		// gap个数据  gap个数据归并
		for (int j = 0; j < n; j += 2 * gap)
		{
			// 归并 取小的尾插
			int begin1 = j, end1 = j + gap - 1;
			int begin2 = j + gap, end2 = j + 2 * gap - 1;

			// 第一组越界
			if (end1 >= n)
			{
				break;
			}

			// 第二组全部越界
			if (begin2 >= n)
			{
				break;
			}

			// 第二组部分越界
			if (end2 >= n)
			{
				// 修正一下end2,继续归并
				end2 = n - 1;
			}


			int i = j;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[i++] = a[begin1++];
				}
				else
				{
					tmp[i++] = a[begin2++];
				}
			}

			while (begin1 <= end1)
			{
				tmp[i++] = a[begin1++];
			}

			while (begin2 <= end2)
			{
				tmp[i++] = a[begin2++];
			}

			// 拷贝回原数组 -- 归并哪部分就拷贝哪部分回去
			memcpy(a+j, tmp+j, (end2-j+1)*sizeof(int));
		}

		gap *= 2;
	}

	free(tmp);
	tmp = NULL;
}

7、快速排序

基本思路:

再待排序的元素中选取一个基准(key),把比基准小的值放在其左边,其他的放在其右边。

7.1 hoare法

动图如下:

 代码如下:

int PartSort1(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	//printf("[%d,%d]-%d\n", left, right, mid);

	Swap(&a[left], &a[mid]);

	int keyi = left;
	while (left < right)
	{
		// 6 6 6 6 6
		// R找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}

		// L找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		if (left < right)
			Swap(&a[left], &a[right]);
	}

	int meeti = left;

	Swap(&a[meeti], &a[keyi]);

	return meeti;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}


		int keyi = PartSort1(a, begin, end);
		//递归区间[begin, keyi-1] keyi [keyi+1, end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
}

7.2 挖坑法

动图如下:

 代码如下:

int PartSort2(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

	int key = a[left];
	int hole = left;
	while (left < right)
	{
		// 右边找小,填到左边坑
		while (left < right && a[right] >= key)
		{
			--right;
		}

		a[hole] = a[right];
		hole = right;

		// 左边找大,填到右边坑
		while (left < right && a[left] <= key)
		{
			++left;
		}

		a[hole] = a[left];
		hole = left;
	}

	a[hole] = key;
	return hole;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}


		int keyi = PartSort2(a, begin, end);
		//递归区间[begin, keyi-1] keyi [keyi+1, end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
}

7.3 前后指针

动图如下:

 代码如下:

int PartSort3(int* a, int left, int right)
{
	// 三数取中
	int mid = GetMidIndex(a, left, right);
	Swap(&a[left], &a[mid]);

	int keyi = left;
	int prev = left;
	int cur = left + 1;
	while (cur <= right)
	{
		// 找小
		if (a[cur] < a[keyi] && ++prev != cur)
			Swap(&a[cur], &a[prev]);

		++cur;
	}

	Swap(&a[keyi], &a[prev]);

	return prev;
}


void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}


		int keyi = PartSort3(a, begin, end);
		//递归区间[begin, keyi-1] keyi [keyi+1, end]
		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
}

7.4优化思路

a、三数取中

快速排序的时候,取key值的时候每次都是最小或者最大的,那么每次递归的就再key的左边或者右边递归,在这种情况下时间复杂度接近O(n^2);

代码如下:

int GetMidIndex(int* a, int left, int right)
{
	int mid = left + (right - left) / 2;
	if (a[left] < a[mid])
	{
		if (a[mid] < a[right])
		{
			return mid;
		}
		else if (a[left] > a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
	else // a[left] >= a[mid]
	{
		if (a[mid] > a[right])
		{
			return mid;
		}
		else if (a[left] < a[right])
		{
			return left;
		}
		else
		{
			return right;
		}
	}
}

b、插入排序

void QuickSort(int* a, int begin, int end)
{
	if (begin >= end)
	{
		return;
	}
    
	if (end - begin <= 8)
	{
		InsertSort(a + begin, end - begin + 1);
	}
	else
	{
		int keyi = PartSort(a, begin, end);
		//[begin, keyi-1] keyi [keyi+1, end]

		QuickSort(a, begin, keyi - 1);
		QuickSort(a, keyi + 1, end);
	}
}

猜你喜欢

转载自blog.csdn.net/weixin_58250064/article/details/126888297