排序算法(C语言)

目录

插入排序

直接插入排序

希尔排序

选择排序

选择排序

堆排序

交换排序

冒泡排序

快速排序

Hoare快排

快排挖坑法

前后指针快排

归并排序

归并非递归

非比较排序

计数排序


(本文排序默认排升序)

插入排序

直接插入排序

直接插入排序就像是摸牌,一个一个插入,如何保证手上的牌有序,就需要插入的牌从左往右比较,如果比插入的小就替换,直到最后一个数字插入

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

void InsertSort(int* a, int n)
{
	for (int end = 1; end < n; end++)
	{
		int tmp = a[end];
		for (int i = 0; i < end; i++)
		{
			if (a[end] < a[i])
			{
				Swap(&a[end], &a[i]);
			}
		}
	}
}

希尔排序

希尔排序逻辑是将数分组排序,如图中一样,将间隔gap=3的数组成一组,可以将大的数更快移到右边,小的数更快移到左边。但这样并不能时数组完全有序

当gap越小时,就越接近有序,最后gap==1( gap/2 或者 gap/3+1 使得最后gap==1)时,就是插入排序,使数组完全有序

时间复杂度O(N^1.3)

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap > 1)
	{
		gap /= 2;
		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;
		}
	}
}

选择排序

选择排序

1、假设数组最左边位置left为最小的数,然后去跟数组后面的数比较,如果有比这个数小的,用min记录其位置

     假设数组最右边位置right为最小的数,然后去跟数组前面的数比较,如果有比这个数大的,用max记录其位置

2、走完一次循环后交换left跟min位置的数,交换right跟max位置的数,使得最左边为最小的数,最右边为最大的数

3、接着数组从left+1到right-1开始循环,找出次小次大的数,直到left与right相遇,排序结束

时间复杂度O(N^2)

void SelectSort(int* a, int n)
{
	int left = 0;
	int right = n - 1;
	int max = left, min = right;
	while (left < right)
	{
		min = left;
		for (int i = left; i < right; i++)
		{
			if (a[min] > a[i])//把小的数放在左边
			{
				min = i;
			}

			if (a[max] < a[i])
			{
				max = i;
			}
		}

		Swap(&a[min], &a[left]);
		//left与max重叠
		if (left == max)
		{
			max = min;
		}
		Swap(&a[max], &a[right]);

		right--;
		left++;
	}
}

堆排序

1、向上调整建大堆,这样就可以把数组中最大的数放在根节点

2、把第一个数跟最后一个数交换,使数组最后的数是最大的数

3、向下调整(排好的数不需要再调整),让根节点是最大的数

循环交换和向下调整,直到所有数排好顺序

时间复杂度O(NlogN)

void Adjustup(int* a, int child)
{
	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
		}
		child = parent;
	}
}

Adjustdown(int* a, int n)
{
	int parent = 0;
	int child = parent * 2 + 1;

	while (child+1<n&&child>0)
	{
		if (a[child + 1] > a[child])//选出较大的子节点
		{
			child = child + 1;
		}

		if (a[parent] < a[child])
		{
			Swap(&a[parent], &a[child]);
		}
		parent = child;
		child = parent * 2 + 1;
	}
}

void HeapSort(int* a, int n)
{
	//向上调整建大堆
	for (int i = 0; i < n; i++)
	{
		Adjustup(a, i);
	}

	//排序
	int end = n - 1;

	while (end >= 0)
	{
		Swap(&a[0], &a[end]);
		Adjustdown(a, end);
		end--;
	}
}

交换排序

冒泡排序

数组最开始两两比较,记录较大值的位置,往后走如果有更大的数就记录更大数的值,遍历完一遍数组后就找到最大的值。把最后一个元素的值跟最大的值交换,循环直到排好所有数

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

}

快速排序

Hoare快排

1、把数组第一个数位置记为key,left从key之后开始找比key位置大的数,right从数组最后往前面找比key位置小的数

2、交换left跟right位置的数,循环直到left与right相遇

3、交换key与相遇位置的值,这样key的值就放在正确的位置,前面数比key位置数小,后面数比key位置数小

4、最后分别递归key之前跟key之后的数,递归返回的条件是left>=right

注意:这里有一个细节,怎样使得相遇时的点比key的值要小呢?如果让left先走,但是当没有找到大的数就相遇,相遇时right大小就并不能确定

 让right先走找小的值就可以解决(让right作为key时,让left先走,就能让相遇时的值比key要大)

void QuickSort(int* a, int left, int right)
{
	if (left >= right)
	{
		return NULL;
	}
	int begain = left;
	int end = right;
	int key = left;
	while (left < right)
	{
		while (left < right && a[right] >= a[key])//右边找小
			right--;

		while (left < right && a[left] <= a[key])//左边找大
			left++;

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

	Swap(&a[key], &a[left]);
	key = left;
	//
	QuickSort(a, begain, key - 1);
	QuickSort(a, key + 1, end);
}

快排挖坑法

与上一个类似,不是交换数据,而是形成坑位

单趟

 1、将第一个作为key,形成坑位

2、让右边找小值放到坑位中,自己形成新的坑位

3、让左边找大值放到坑位中,自己形成新的坑位

4、左右相遇时,所在的位置是一个坑位,把key放到坑位中

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

	int key =a[left];
	int begain = left;
	int end = right;
	while (left < right)
	{
		while (left < right && a[right] >= key)
		{
			right--;
		}
		a[left] = a[right];

		while (left < right && a[left] <= key)
		{
			left++;
		}
		a[right] = a[left];
	}

	a[left] = key;
	int keyi = left;

	return keyi;
}

前后指针快排

单趟

 1、定义两个指针如图所示,cur在prev前一个位置

 2、cur往后找小于key的值,因为cur之前的数都是比key要大的,所以直接与++prev交换

3、当cur越界之后,prev所在的位置就是比key小的数,将key与prev的数交换

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

	int prev = left;
	int cur = left + 1;
	int keyi = left;

	while (cur <= right)
	{
		if(a[cur] < a[keyi])//cur找小
		{
			Swap(&a[prev], &a[cur]);
		}

		cur++;
	}
	Swap(&a[prev], &a[keyi]);
	keyi = prev;

	return keyi;
}

最开始的Hoare快排有一个问题,当数组为有序时 ,就会对有序数组重复遍历,时间复杂度会达到O(N^2)

有两种方法可以解决这个问题

1、随机key

用rand生成[left,right]之间的随机数

left + (rand() % (right - left));这部要加left才能保证生成的随机数在[left,right]范围内

int randi =left + (rand() % (right - left));
	Swap(&a[left], &a[randi]);

2、三数取中

在最左边、中间、最右边三个数中找到一个中等大小的值,然后与数组最左边的数交换

int GetMidNum(int* a, int left, int right)
{
	//找出中等的数
	int mid = (right - left) / 2;
	if (a[right] > a[left])
	{
		if (a[mid] > a[right])
		{
			return right;
		}
		else if (a[left] > a[mid])
		{
			return left;
		}
		else
		{
			return mid;
		}
	}
	else 
	{
		if (a[mid] > a[left])
		{
			return left;
		}
		else if (a[right] > a[mid])
		{
			return right;
		}
		else
		{
			return mid;
		}
	}
}

归并排序

1、递归分解数组到两个之间比较,把小的先拷贝到新数组tmp中,再把大的拷到tmp中,然后把tmp中的数据拷贝回原数组

2、两个与两个的区间比较,先拷贝小的数,然后拷贝大的数到tmp,把tmp数据拷贝回原数组

3、递归回到数组分成两个区间比较之后完成排序

注:两个区间比较完之后,可能有一个区间的数没有拷贝到tmp中,还需要写两个while循环来把没有拷贝完的拷贝到tmp

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

	int mid = (begain + end) / 2;
	//区间 [begain,mid][mid+1,end]
	int begain1 = begain, end1 = mid;
	int begain2 = mid + 1, end2 = end;
	_MergeSort(a, tmp, begain1, end1);
	_MergeSort(a, tmp, begain2, end2);

	int i = begain;
	while (begain1 <= end1 && begain2 <= end2)
	{
		if (a[begain1] > a[begain2])
		{
			tmp[i++] = a[begain2++];
		}
		else
		{
			tmp[i++] = a[begain1++];
		}
	}

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

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

	memcpy(a + begain, tmp + begain, sizeof(int) * (end-begain+1));
}


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

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

归并非递归

时间复杂度O(NlogN)

归并思想是两两区间归并,定义四个变量来控制这两个区间,[begain1,end1][begain2,end2]

非递归相比于递归方式难点在于区间的控制,画图的方式能够更好理解

易错点:归并后拷贝回去是,begain1已经改变,i记录的是每次归并前begain的初始位置,

void MergeSortNonR(int* a, int begain, int end, int n)
{
	int* tmp = (int*)malloc(sizeof(int) * n);
	if (tmp == NULL)
	{
		perror("malloc fail");
	}
	
	int gap = 1;
	while (gap < n)
	{
		for (int i = 0; i < n; i += 2 * gap)
		{
			int j = i;
			int begain1 = i, end1 = i + gap - 1;
			int begain2 = i + gap, end2 = i + 2*gap - 1;

			if (end1 > n-1 || begain2 > n-1)
			{
				break;
			}

			if (end2 > n-1)
			{
				end2 = n-1;
			}

			while (begain1 <= end1 && begain2 <= end2)
			{
				if (a[begain1] > a[begain2])
				{
					tmp[j++] = a[begain2++];
				}
				else
				{
					tmp[j++] = a[begain1++];
				}
			}

			while (begain1 <= end1)
			{
				tmp[j++] = a[begain1++];
			}

			while (begain2 <= end2)
			{
				tmp[j++] = a[begain2++];
			}

			memcpy(a + i, tmp + i, sizeof(int) * (end2-i+1));//i的位置就是begain的初始位置
		}

		gap *= 2;
	}

	 
	free(tmp);
	tmp = NULL;
}

非比较排序

计数排序

用一个数组tmp记录排序数组中元素出现的次数,该数组大小就是排序数组最大值减最小值

tmp中的数组元素加min就是排序数组中的元素 ,之后只需要将tmp中的数放到排序数组

计数排序只适合在元素较集中整形的数组,不适合分散数组跟整形数组

void CountSort(int* a, int n)
{
	int max = a[0], min = a[0];
	int i = 1;
	for (int i = 1; i < n; i++)//记录最大 最小值
	{
		if (a[i] > max)
		{
			max = a[i];
		}

		if (a[i] < min)
		{
			min = a[i];
		}

	}

	int range = max - min;
	int* tmp = (int*)malloc(sizeof(int) * range);
	if (tmp == NULL)
	{
		perror("malloc fail");
		return ;
	}

	memset(tmp, 0, sizeof(int)*range);

	for(int i=0;i<n;i++)
	{
		tmp[a[i]-min]++;
	}

	int j = 0;
	for (int i = 0; i < range-1; i++)//排序
	{
		while (tmp[i]--)
		{
			a[j++] = i + min;
		}
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值