六大排序算法

目录

1.直接插入排序

2.希尔排序 

3.选择排序

4.堆排序 

5.冒泡排序

6.快速排序

1.hoare版本

2 .挖坑法:

3.前后指针法

7.归并排序

1.递归

2.非递归 

时间和空间复杂度汇总


1.直接插入排序

步骤:

1.从第二个元素开始,与前面的元素依次比较,如果比前面的元素小就向前移动

2.当该元素大于前一个元素时,插入

3.重复上述过程

代码实现:

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

时间复杂度: 

 时间复杂度:最坏情况O(N^2)——逆序(遍历每个元素N * 每个元素与前面N个数比较)

                       最好情况O(N)——   升序(遍历每个元素N)

2.希尔排序 

步骤:

1.选定一个gap作为跨度距离(通常选 n/3+1  or  n/2),然后将所有距离为gap分在同一组 ,对每一组的元素进行直接插入排序

2.重复上述操作 到gap==1时,整个序列被分到一组,相当于进行一次直接插入排序,排序完成

在这里插入图片描述

思路:

现将待排序列进行预排序,使待排序列接近有序,最后对该序列进行一次插入排序,最后一次插入排序的时间复杂度可以看做最好情况O(N)

 代码实现:

void ShellSort(int* a, int n)
{
	// gap > 1 预排序
	// gap == 1 直接插入排序

    int gap = n;
	while (gap > 1)
	{
		gap = gap / 3 + 1;

		for (int i = 0; i < n - gap; i++)
		{
			int end = i;
			int tmp = a[i + gap];
			while (end >= 0)
			{
				if (tmp < a[end])
				{
					a[end + gap] = a[end];
					end -= gap;
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
		}
	}
}

时间复杂度: 

 

 一共有log2 N层或者log3 N层 每一次操作的时间复杂度为N

 近似可以看作:O(N*logN)

 经过更复杂的计算,最终的平均时间复杂度为:O(N^1.3)

3.选择排序

步骤:在每一次遍历中,选择本次遍历数列的的最小值,交换放到放到数列的最前端
我们可以一趟选出最大(放在末端)的和最小(放在前段)两个值,这样可以使得效率增大一倍

代码实现:

//最坏 O(N^2)
//最好 O(N^2)
void SelectSort(int* a, int n)
{
	//记录最小值和最大值的位置
	int left = 0, right = n - 1;
	while (left < right)
	{
		//将最左边的值认为是最大的,最右边的值认为是最小的
		//如果有比它还小的数 更新下标
		int mini = left, maxi = right;
		for (int i = left+1; i <= right; i++)
		{
			if (a[i] < a[mini])
			{
				mini = i;
			}
			if (a[i] > a[maxi])
			{
				maxi = i;
			}
			
		}
		if (left == maxi)//如果i==maxi, 交换后要修正一下
		{
			maxi = mini;
		}
		Swap(&a[left], &a[mini]);
		Swap(&a[right], &a[maxi]);
		++left;
		--right;
	}
}

注意:如果选择的最大值和left重叠,需要修正maxi的位置 

时间复杂度:   

 最好情况:O(N^2)                                                                                                                     最坏情况:O(N^2)(每选一个数N * 遍历剩下所有数N)

4.堆排序 

见这篇博客:

5.冒泡排序

步骤:

每一次从数列开头进行比较,如果左边大于右边,就交换

每一次循环结束后,数列的最后端就确立了

代码实现:

void BubbleSort(int* a, int n)
{
	int flag = 0;
	for (int j = 0; j < n - 1; j++)
	{
		for (int i = 1; i < n-j; i++)
		{
			if (a[i] < a[i - 1])
			{
				Swap(&a[i], &a[i - 1]);
				flag = 1;
			}
		}

		if (flag == 0)
		{
			break;
		}
	}
}

建立一个flag,如果本次循环一次都没有交换的话,说明数列已经有序,可以减少循环次数

时间复杂度:

时间复杂度:最坏情况:O(N^2)(每个数都要遍历一次N*每一趟操作的时间复杂度N)

                      最好情况:O(N)

6.快速排序

1.hoare版本

步骤:

1.将数列的第一个值作为key

2.设置左右指针,分别指向数列的起始和末尾

3.R先向前移动,找到小于key位置,L再向后移动,找到大于key的位,换L和R位置的数值

4. 重复上述行为

5.L与R相遇时,将该位置的值与key交换

思路:将key的值看做数列的中间值,将>key 的值都放在数列的左边 <key的值都放在数列的右边

 代码实现:

int PartSort1(int* a, int left, int right)
{
	//当数组为顺序有序时,时间复杂度为O(N^2) 
	//因此选择keyi,让它变为无序

	//随机选keyi(但仍有几率为顺序数组)
	//int randi = left + rand() % (right - left);
	//Swap(&a[left], &a[randi]);
	
	//三数取中
	//int midi = GetMidNumi(a, left, right);
	//Swap(&a[left], &a[midi]);

	int keyi = left;
	while (left < right)
	{
		//左边找小
		while (left < right && a[right] >= a[keyi])
		{
			--right;
		}
		//右边找大
		while (left < right && a[left] <= a[keyi])
		{
			++left;
		}

		Swap(&a[left], &a[right]);
	}
	Swap(&a[left], &a[keyi]);
	keyi = left;

	return keyi;
}
void QuickSort(int* a, int left, int right)
{
	if (left >= right)
		return;
	
	int keyi = PartSort3(a, left, right);
	QuickSort(a, left,keyi-1);
	QuickSort(a, keyi+ 1,right);
}

时间复杂度:

快速排序的时间复杂度在O(nlogn)~ O(n^2)

深度:logN ---> N(轴是区间的最大值或者最小值)

每一次操作都需要遍历一次剩下的所有元素,这个操作的时间复杂度是O(n)

最坏情况为:当对于每一个区间选取的轴刚好就是这个区间的最大值或者最小值 

解决方案:

1.随机选keyi

//随机选keyi(但仍有几率为顺序数组)
	int randi = left + rand() % (right - left);
	Swap(&a[left], &a[randi]);

2.三数取中

//三数取中
int GetMidNumi(int* a, int left, int right)
{
	int midi = (right - left) / 2;
	if (a[left] > a[midi])
	{
		if (a[midi] > a[right])
		{
			return midi;
		}
		else if (a[left] > a[right])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
	else//a[left] < a[midi]
	{
		if (a[midi] < a[right])
		{
			return midi;
		}
		else if (a[right] > a[left])
		{
			return right;
		}
		else
		{
			return left;
		}
	}
}

int midi = GetMidNumi(a, left, right);
Swap(&a[left], &a[midi]);

2 .挖坑法:

步骤:

 1.记录key的值

2.设置左右指针,走法与hoare相同,当R找到比key小的值,将R所在位置的值放入坑中,同时坑位变为R所在位置,L类似(相遇一定在坑中) 

思路:甩值变坑

 代码实现:

int PartSort2(int* a, int left, int right)
{
	int begin = left, end = right;

	int midi = GetMidNumi(a, left, right);
	Swap(&a[left], &a[midi]);

	int key = a[left];//记录key的值
	int holei = left;//记录坑的位置
	while (left < right)
	{
		//左边找小
		while (left < right && a[right] >= key)
		{
			--right;
		}
		a[holei] = a[right];
		holei = right;
		//右边找大
		while (left < right && a[left] <= key)
		{
			++left;
		}
		a[holei] = a[left];
		holei = left;
	}
	a[holei] = key;//一定在空的坑位相遇

	return holei;
}

3.前后指针法

步骤:

  1.cur遇到比key小的值,++prev,将prev所在位置的值和cur交换,cur++

  2.当cur遇到比key大的值,cur++

  3.当cur越界时,将prev的值和key交换

 说明:

  1.prev要么紧跟着cur(prev的下一个就是cur)

  2.prev与cur相隔一段 >key 的值区间

代码实现: 

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

	int key = a[left];
	int prev = left, cur = left + 1;
	while (cur <= right)
	{
		if (a[cur] < key && ++prev!=cur)
		{
			Swap(&a[prev], &a[cur]);
		}
		++cur;
	}
	Swap(&a[left], &a[prev]);

	return prev;
}

7.归并排序

1.递归

步骤:递归的第一层,将n个数划分为2个子区间,每个子区间的数字个数为n/2;
           递归的第二层,将n个数划分为4个子区间,每个子区间的数字个数为n/4;
           递归的第三层,将n个数划分为8个子区间,每个子区间的数字个数为n/8;
           递归的第logn层,将n个数划分为n个子区间,每个子区间的数字个数为1;                                       之后将每两个相邻子区间进行合并(先左后右)

分而治之(分:将问题分成一些小的问题然后求解。治:分阶段得到的答案修补在一起)

思路:从后往前,递归

代码实现: 

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);

	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, sizeof(int)*(end-begin+1));
}
void MergeSort(int* a, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail\n");
	}
	
	_MergeSort(a, 0, n - 1, tmp);

	free(tmp);
}

时间复杂度:       

时间复杂度:O(N*logN) ——— 一共有logN层,每层遍历N个

2.非递归 

步骤:用gap将原数列分为不同的组,排序,在将排好的数列拷贝到原数列中

思想:从前往后

代码实现: 

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

	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i+=2*gap)
		{
			int begin1 = i, end1 = i + gap - 1;
			int begin2 = i + gap, end2 = i + 2 * gap - 1;

			/*if (end1 > n-1)
			{
				end1 = n-1;
				begin2 = n;
				end2 = n-1;
			}
			else if (begin2 > n - 1)
			{
				begin2 = n;
				end2 = n - 1;
			}
			else if (end2 > n - 1)
			{
				end2 = n - 1;
			}*/
			if (end1 > n - 1 || begin2 > n - 1)
			{
				break;
			}
			if (end2 > n - 1)
			{
				end2 = n - 1;
			}
			printf("[%d][%d],[%d][%d]  ", begin1, end1, begin2, end2);
			int j = i;
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] < a[begin2])
				{
					tmp[j++] = a[begin1++];
				}
				else
				{
					tmp[j++] = a[begin2++];
				}
			}
			while (begin1 <= end1)
			{
				tmp[j++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[j++] = a[begin2++];
			}

			memcpy(a+i , tmp+i, sizeof(int)*(end2-i+1));//归并一部分,拷贝回去一部分

		}
		printf("\n");
		//memcpy(a , tmp, sizeof(int) * n);//全部归并完拷贝
		gap *= 2;
	}

	free(tmp);
}

时间和空间复杂度汇总

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值