排序说明(希尔、快排等c语言版)

目录

1.插入排序

1.1直接插入排序

1.1.1特性:

1.2希尔排序

1.2.1特性:

2.选择排序

2.1简单选择排序

2.1.1特性:

2.2堆排序

2.2.1特性:

3.交换排序

3.1.冒泡排序

3.1.1特性

3.2快速排序

3.2.1递归版(1):

3.2.2递归版本(2)

3.2.3递归版本3:

3.2.4非递归版本:

3.2.5特性:

4.归并排序

 4.1递归版本

4.2非递归版本:

4.3特性:

5.计数排序

5.1特性:


1.插入排序

1.1直接插入排序

void InsertSort(int* a, int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int end = i;
		int tmp = a[end + 1];
		while (end >= 0)
		{
			if (a[end] > tmp)
			{
				a[end + 1] = a[end];
				end--;
			}
			else
			{
				break;
			}
		}
		a[end + 1] = tmp;
	}
	
}
插入排序,是在原数组上进行操作的

从下标0开始,end=i,end是已经排好序的下标
end+1是准备插入的数据下标,赋给tmp,
通过while循坏,让end位置的数据与tmp比较,这里是升序
所以,只要end位置下标数据大于tmp,就让end位置的数据
覆盖到end+1上,让end位置空出来,end再--,直到end下标数据大于等于
tmp,就直接break,因为上次循环end已经--了,此时的end+1就是刚刚空出来的end位置
于是让tmp数据直接覆盖到end+1即可。
注意每次都会让一个位置空出来,其他数据往后挪,直到遇到合适位置插入数据

1.1.1特性:

空间复杂度是:O(1)

时间复杂度:O(N^2)

稳定性:稳定

ps:因为没有开辟新空间,所以空间是O(1);

时间复杂度最坏情况是逆序的数据排升序,那么每次都要把数据遍历到0下标位置才行,所以加起来接近N^2;

稳定性,因为遇到相等值,end跟tmp存的数据一样,tmp数据是覆盖到end+1位置的,相对位置没有变

1.2希尔排序

void ShellSort(int* a, int n)
{
	int gap = n;
    //gap是一个间隔的大小
    //通过gap,把一个数据分成数量不等的多组数据
    //希尔排序就是在插入排序的前提下,加入了预排序
    //因为插入排序对越接近有序的数据,效率越高
    //于是通过预排序,让数据越发接近有序
    //gap=1就可以直接当做是插入排序了
	while(gap > 1)
	{
		gap /= 2;//gap/=3 +1
        //gap的变化没有一个好的判断,现在默认每次/2,/3+1也行
        //  /3+1是要保证最少能1
		for (int i = 0; i < n - gap; i++)
		{
            //注意,i要小于n-gap,以免越界访问
			int end = i;
			int tmp = a[end + gap];
            //这边的写法可以参考插入排序
            //只是原来的插入排序,每次都是对+1位置的数据与已排序的数据
            //进行比较,找合适位置。
            //而这里,当前位置的+gap,才是这组数据应该待插入的数据
      
			while (end >= 0)
			{
				if (a[end] > tmp)
				{
					a[end + gap] = a[end];
					end -= gap;
                    //当tmp小于end位置数据,让end位置数据覆盖到end+gap位置
                    //因为最初的end+gap数据已经存在tmp了,所以放心的往后挪数据即可
                //end要-=gap,因为当前的数据在当前这组数据里的前一个数据是在数组里
                //是通过gap大小隔开的。
				}
				else
				{
					break;
				}
			}
			a[end + gap] = tmp;
            //end+gap的位置永远是待覆盖的
		}
	}
}

1.2.1特性:

空间复杂度:O(1)

因为没有开辟额外空间。

时间复杂度:O(N^2)

注意,这里是最坏情况,是逆序时,在排接近有序的数据时,反而是非常快的

最好情况是O(1.3)

稳定性:不稳定

因为有可能两个相等的数据被分在了不同的组里,这时就有可能改变相对顺序

2.选择排序

2.1简单选择排序

void SelectSort(int* a, int n)
{
	int begin = 0, end = n - 1;
	
	while (begin < end)
	{
    int min = begin, max = begin;
    //设置min和max,记录begin和end范围内的最大值和最小值    
    //注意,每次必须都重新把max和min的下标改下,以免出现位置错误
		for (int i = begin + 1; i <= end; i++)
		{
			if (a[i] > a[max])max = i;
			if (a[i]< a[min])min = i;
		}
        //记录begin和end范围内的最大值和最小值
		Swap(&a[begin], &a[min]);
        //把最小值跟begin位置数据交换
        //把小的放在前面
		if (max == begin)max = min;
        //防止begin位置就是max值   
        //否则后面max位置存的是min值,min位置存的是max值
		Swap(&a[end], &a[max]);
        //让max位置数据跟end位置数据交换,把max的放在后面
          
		begin++;
		end--;
        //排完两个数,缩减范围
	}
    //循坏条件采用begin<end,当begin=end,说明已经排完了
}

2.1.1特性:

空间复杂度:O(1)

因为没有开辟新空间

时间复杂度:O(N^2)

每次都会遍历begin-end内的数据,一共n次,那么加起来就会接近N^2

稳定性:不稳定

2.2堆排序

void AdjustDown(int* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;

		}
		else
		{
			break;
		}
	}
}
//向下调整算法,就是当前面parent的节点数据与孩子比较,
//根据升序降序,建小堆还是大堆,把小的或者大的数据往下放
//具体可以看下我的二叉树的文章


void Heapsort(int* a, int size)
{
	assert(a);
	for (int i = ((size - 1) - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, size, i);
	}
	int end = size - 1;
	while (end > 0)
	{
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}
//这个我在二叉树的文章里写了,这里简单说下
//通过向下调整算法,把数组最后一个元素当成最后一个叶节点
//然后从最后一个分支节点开始向下调整,这样对于这个分支节点
//所构成的子树来说,已经把子树里数据,按大堆或小堆的规则排列了
//这样不停的往前走,直到从数组下标0的位置开始向下调整,这样
//整个数组都被排列成大堆或小堆了

2.2.1特性:

空间复杂度:O(1)

时间复杂度:O(N*logN)

用满二叉树算,2^h -1=N,h=log2(N+1),而建堆需要O(N)的复杂度,而堆排序,需要把堆顶元素和数组最后一个交换,最终要循环N次,所以,最终是O(N+N*logN),即

O(N*logN)

稳定性:不稳定

对于相等的数据,假如根节点和根节点右孩子相等,那么根节点被置换后,右孩子上位,也被置换,那么两者的相对位置就被改变了

3.交换排序

3.1.冒泡排序

void BubbleSort(int* a, int n)
{
	for (int j = 0; j < n; j++)
	{
    //最外面的j是用来控制里面for循环的范围的,
    //对已经排好的数据,不要循环到
		int s1 = 0;
        //假如有些数据只需要一次循坏就完成有序了
        //所以这时候判断有没有进行过一次交换,没有
        //交换过,就直接退出循环即可。
		for (int i = 1; i < n-j; i++)
		{
			if (a[i - 1] > a[i])
			{
				Swap(&a[i - 1], &a[i]);
				s1 = 1;
			}
            //注意,这里的范围,要注意下面的i-1,还是i+1,这决定了最初是1开始
            //还是0开始,也决定了i最大是n-1还是n-2
		}
		if (s1 == 0)break;
	}
}
冒泡排序的思想,就是将最大或最小的数,放在最后

3.1.1特性

空间复杂度:O(1)

没有开辟新空间

时间复杂度:O(N^2)

i循坏每次都要遍历接近整个数组的大小,约下来,差不多就是N^2了

稳定性:稳定

对于重复性数据,不会进行交换,就算进行交换,也是第二个重复的数据跟后面的数据交换,不会改变相对位置

3.2快速排序

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

	int key = PartSort3(a, begin, end);
	QuickSort(a, begin, key - 1);
	QuickSort(a, key + 1, end);
}
快排的思想,就是将关键值放在一个合适的位置,让关键值最终的位置左边都小于等于关键值,右边大于关键值
再把关键值左边的部分找个新的关键值,左边小于,右边大于,
关键值部分右边也是,找个新关键值,左边大于,右边小于
不停递归。
这是个通用的函数,具体还是partsort函数部分内容,因为
关于这部分的操作有很多版本,我们一个个讲

3.2.1递归版(1):

int GetMidi(int* a, int begin, int end)
{
	int midi = (begin + end) / 2;
	if (a[begin] > a[midi])
	{
		if (a[midi] > a[end])return midi;
		else if (a[begin] > a[end])return end;
		else return begin;
	}
	else
	{
		if (a[midi] < a[end])return midi;
		else if (a[begin] < a[end])return end;
		else return begin;
	}
}
三数取中,将begin位置和end位置,还有2者中间的位置,
3个位置中找个中间值
可以避免,选的关键值太小,增加时间复杂度
int PartSort1(int* a, int begin, int end)
{
	int left = begin, right = end;
		int key = begin;
    //left和right是指向下标
		int midi = GetMidi(a, begin, end);
		Swap(&a[midi], &a[begin]);
    //三数取中,把中间值跟begin位置交换
		while (left < right)
		{
			while (left<right && a[right]>=a[key])
			{
				--right;
			}
    //当left还小于right且,right还大于等于key位置值,则继续--
    //往前找,当小于的时候,停下
			while (left < right && a[left] <= a[key])
			{
				++left;
			}
    //同理,从左边开始找大,找到停下
			Swap(&a[left], &a[right]);
		}
    //
		Swap(&a[left], &a[key]);
    //key位置的值跟left位置的值交换,
    //为什么保证left最终停的位置一定小于等于key呢
    //因为我们是先将right--,再left--
    //那么只有两种情况,
    //第一种,right先停下,left走到right位置,这时,left和
    //right相等,right是找到小的停下,那么key位置和left交换
    //就不会出事
    //第二种,right先遇到left,这是left在上次循坏已经跟right交换过了
    //这时left位置的数据还是比关键值小的,退出循环后
    //left和key交换,还是把比key小的值放入key位置,key值放在left位置上
    //这样key就会放在数组中间位置上
		key = left;
    //让left下标赋值给key,left下标此时存的是key值,
    //再返回下标
		return key;
}
霍尔版本

3.2.2递归版本(2)

//挖坑法
int PartSort2(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
    //三数取中
	int hole = begin;
	int key = a[begin];
    //挖坑,第一个坑begin位置
    //提前把key值存下
	while (begin < end)
	{
		while (begin < end && a[end] >= key)
		{
			end--;
		}
    //找小
		a[hole] = a[end];
		hole = end;
    //把end位置的值放在hole上,hole位置上的值都是可以被随意覆盖的,因为
    //提前被覆盖到别的位置或已经存起来了
		while (begin < end && a[begin] <= key)
		{
			begin++;
		}
    //找大
		a[hole] = a[begin];
		hole = begin;
    //值放入hole下标
	}
	a[hole] = key;
    //最后把key值放在hole上
    //返回hole,hole就是关键值下标
	return hole;
}

3.2.3递归版本3:

//前后指针
int PartSort3(int* a, int begin, int end)
{
	int midi = GetMidi(a, begin, end);
	Swap(&a[midi], &a[begin]);
    //三数取中
	int prev = begin;
	int cur = begin + 1;
	int key = begin;
    //prev下标位置是用来跟cur交换的
    //prev前的数据都是比key小的
	while (cur <= end)
	{
		if (a[cur] < a[key])
		{
			prev++;
			Swap(&a[prev], &a[cur]);
			cur++;
		}
		else
		{
			cur++;
		}
      //  除非找到比key值小的,否则cur都继续往后走
       //如果找到了,那就把prev++,让cur位置的值跟prev交换

	}
	Swap(&a[prev], &a[key]);
    //同理,因为prev在++前,必是小于key的值
    //交换即可
	key = prev;
    //同理prev是key值合适的位置,返回即可
	return key;
}

3.2.4非递归版本:

​
void QuickSortNotR(int* a, int begin, int end)
{
	ST s1;
	STInit(&s1);
	STPush(&s1, end);
	STPush(&s1, begin);
    //利用栈的后进先出特性,先把end和begin下标放进去
	while (!STEmpty(&s1))
	{
		int left = STTop(&s1);
        //left就是后进的begin
		STPop(&s1);
		int right = STTop(&s1);
        //right就是先进的end
		STPop(&s1);
		int keyi = PartSort3(a, left, right);
        //利用前面写的内容
		if (left < keyi - 1)
		{
			STPush(&s1, keyi - 1);
			STPush(&s1, left);
		}
        //左部分放进去,先放keyi-1,再放left
		if (right>keyi+1)
		{
			STPush(&s1, right);
			STPush(&s1, keyi+1);
		}
        //右部分放进去,先放right,再放keyi+1,
	}
	STDestory(&s1);
}

​

3.2.5特性:

空间复杂度:O(logN):

递归没有开辟新空间,非递归开辟了,因为递归时空间是可以复用的,但非递归时不行

时间复杂度:O(N*logN)

logN层,每层数据加起来都是N

稳定性:不稳定

因为相同数据相同数据,left和right是不管的,只会++或--

4.归并排序

 4.1递归版本

void _MergeSort(int* a, int begin,int end, int *tmp)
{
	if (begin >= end)return;
    //因为begin=end,说明,此时只有1个数据,已经是有序了,返回即可
    //大于,说明,下标超了,也是只有一个数据,不需要
	int mid = (begin + end) / 2;
	_MergeSort(a, begin, mid ,tmp);
	_MergeSort(a, mid+1, end ,tmp);
    //从中间位置开始分,把左边部分和右边部分递归
	int begin1 = begin, end1 = mid, 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++];
		}
	}
    //把归并后的结果放在tmp数组
	while (begin1 <= end1)
	{
		tmp[i++] = a[begin1++];
	}
	while (begin2 <= end2)
	{
		tmp[i++] = a[begin2++];
	}
    //因为不清楚哪组数据多点,所以这样写即可,把剩下的数据放入tmp
	memcpy(a + begin, tmp + begin, sizeof(int) * (end - begin + 1));
    //再把tmp数组数据覆盖进原数组
}

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;
}
//归并排序,重点在于归并
//每个数据自身都是有序的,2个数据有序,就是两个数据排序,2组数据,每组数据都有2个已经有序的数据
//再把这两组数据归并,就可以让4个数据有序
//最后令整个数组有序即可

4.2非递归版本:

void MergeSortNonR(int* a, int n)
{
	
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return;
	}
	int gap = 1;
    //这里用gap,在递归里,最后一层,每一个数据都是一组数据
    //这时候意味着gap=1,那么倒数第二层就是gap*2,
	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;
			int index = i;
    //i要以2*gap为间隔,以便进入同层的下一组数据
			if (begin2 >= n)
			{
				break;
			}
			if (end2 >= n)
			{
				end2 = n - 1;
			}
     //因为gap会*2,可能会超下标,下标只有可能是end1,begin2,end2会超
     //而begin2超,end1必超,所以begin2超就直接退出循环即可
     //begin2超,说明只有1组数据,在最初,就是只有1个数据,那么必然有序,后面再归并就好
     //end2超,说明下一组数据没有跟前一组数据数量不一样,那么end2变成n-1即可。
			while (begin1 <= end1 && begin2 <= end2)
			{
				if (a[begin1] <= a[begin2])
				{
					tmp[index++] = a[begin1++];
				}
				else
				{
					tmp[index++] = a[begin2++];
				}
			}
        //两组数据比较,放入tmp
			while (begin1 <= end1)
			{
				tmp[index++] = a[begin1++];
			}
			while (begin2 <= end2)
			{
				tmp[index++] = a[begin2++];
			}
            //剩下的直接放入tmp
			memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));
            //tmp覆盖到原数组,注意,覆盖要从当前第一组数据开始覆盖,以免覆盖了随机值
		}
		gap *= 2;
		printf("\n");
	}
	free(tmp);
}

4.3特性:

空间复杂度:O(N)

因为要开辟n个大小的数组

时间复杂度:O(N*logN)

稳定性:稳定

对于重复的数据,肯定是begin1那组数据在相对位置上在前面的,那么因为<=,所以会优先把begin1组的数据放进去,这样相对位置就不会变了

5.计数排序

void CountSort(int* a, int n)
{
	int min = a[0], max = a[0];
	for (int i = 0; i < n; i++)
	{
		if (a[i] > max)max = a[i];
		if (a[i] < min)min = a[i];
	}
    //先通过遍历,找到最大最小
	int range = max - min + 1;
    //开辟range大小的数组
    //range是所有出现的数据的范围
	int* count = (int*)malloc(sizeof(int) * range);
	if (count == NULL)
	{
		perror("malloc fail");
		return;
	}
	memset(count, 0, sizeof(int) * range);
	for (int i = 0; i < n; i++)
	{
		count[a[i] - min]++;
	}
    //通过a[i]-min的方式,让出现的数据自动计数
	int j = 0;
	for (int i = 0; i < range; i++)
	{
		while (count[i]--)
		{  
			a[j++] = i + min;
		}
	}
    //最后按顺序,输出count数组里相应值次数的下标+min即可。
}

5.1特性:

空间复杂度:O(range)

时间复杂度:O(range/N)

 因为可能出现,数据不集中,那么这时候range是大于N,那么遍历的次数来说,时间复杂度肯定是以range为主,相反,如果数据很集中,可能range比N小很多,那么时间复杂度就是

N为主

稳定性:稳定。

本质上没有排序,只是按顺序输出数组值次罢了,

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值