算法与数据结构 ------排序篇(1)

前言

排序就是将一串数字按照大小递增或递减地排列起来的操作

在了解各种排序算法之前我们需要知道另外一些概念

排序的稳定性:如果说一串数字例如:1 3 4 2 7 6 10 4

如果说进行多次排序,然后第一个四的位置一直在第二个四之前或者之后

那么说明该排序是稳定的

内部排序
数据元素全部放在内存中的排序。
外部排序
数据元素太多不能同时放在内存中,根据排序过程的要求不能在内外存之间移动数据的排序。

一、直接插入排序

大家小时候都玩过扑克吧,当你拿到手牌的时候,绝大多数情况下它是乱序的

为了更容易看出自己可以出的对子、炸弹或看着美观一些通常都会对其进行排序

直接插入排序就类似于扑克牌的整理

最开始你没有手牌,然后抽一张牌,现在你手上就有了一张手牌

再抽一张排,根据手牌和抽的这一张排进行排序;这样不论你抽到的是什么牌,你的手牌都是有序的

举一个例子如果是升序

你的手牌是2,3,6,9

然后你抽到了5

那么你会把5插入到3和6的中间 2,3,5,6,9

把牌插入的过程就可以看为是一趟排序

你有几张牌(数组有几个元素)就要排几趟

换手牌的过程就是拿抽的牌和手牌比,从最后一张比起,如果比手牌大直接放到最后面

比手牌小就让最后一张手牌后移腾出位置,和倒数第二张比,依次往前,直到手牌比抽的牌小

那么一趟排序的过程就是将已经排完序最后一位的元素和还未排序(排完序元素的下一位)比

最后一位大于“插入”的元素,最后一位后移

直到在已经排完序的元素中找到小于“插入”的元素为止

将插入的元素放到那个挪出来的位置

函数实现:

由于要有n趟排序,我们可以用一个for循环实现

一趟排序需要最后一个有序元素,又因为走一趟排序说明有一个数变成有序的

所以我们可以创建一个变量end用for循环的i赋给end

end是数组的下标,"插入"的元素tmp就是a[end+1]

和有序的元素比较是多次进行比较的,所以我们可以while循环

如果最后一个有序的元素大于"插入"的元素,最后一个有序元素后移a[end+1]=a[end]

和倒数第二个有序元素比end--

当比到最前面或者有序元素比“插入”元素小那么跳出循环

跳出循环说明有序元素已经挪好了

就把"插入"的元素放到那个空出来的位置a[end+1]=tmp

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)//end比插入值大
			{
				a[end + 1] = a[end];//后移,end--
				end--;

			}
			else
			{
				//tmp比end指向的值大
				break;
			}
			
		}
		//‘插入的值’比end指向的值大或者end走到数组的前面
		a[end + 1] = tmp;
	}
}

二、希尔排序

希尔排序是在直接插入排序的基础上实现的

主要有两大部分:

1、预排序   让大的数更快地到后面去,小的数更快地到前面去

2、直接插入排序    前面讲过了

预排序:分组排序,间距为gap分为一组

当gap等于1的时候就排好序了,gap等于1就是直接插入排序

举个例子

9 1 2 5 7 4 8 6 3 5

假设gap为3 分为3个组

第一组:9 5 8 5

第二组:1 7 6

第三组:2 4 3

gap为几就分为几个组

gap初始为n

要进行多次预排序,需要循环实现,每次gap除等2,直到gap小于等于1

有多个组且每个组有多个元素,用嵌套循环来实现

一个外循环表组,内循环表组中元素

直接选择排序

内循环代表的是组中元素,预排序就是对分成的gap个组进行直接选择排序

所以内循环套用直接选择排序的代码

但需要注意的是,分成的组元素与元素之间的间隔为gap而不是1

void ShellSort(int* a, int n)
{
	int gap = n;
	while (gap>1)//预排序
	{
		gap /= 2;
		for (int j = 0; j < gap; j++)//gap的初始指向值
		{
			for (int i=j; i < n - gap; i += gap)//gap所指向的组(gap初始指向的值每次下标+gap)
			{
				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;
			}
		}
	}
}

三、冒泡排序

冒泡排序一般是我们最先学习的一个排序,想必大家对其都不陌生吧

思路其实就是从首元素开始前后两个元素相比哪个大就把哪个往后移

每成功移一个元素就是一趟排序

第一趟排完了最大的元素,第二趟排完了次大的......

void BubbleSort(int* a, int n)
{

	for (int i = 0; i < n; i++)
	{
		int exchange = 0;
		for (int j = 1; j < n - i; j++)
		{
			if (a[j - 1] > a[j])
			{
				swap(&a[j - 1], &a[j]);
				exchange = 1;

			}
		}
		if (exchange == 0)
			break;
	}

}

 

 

四、选择排序

选择排序分为直接选择排序和堆排序

1、直接选择排序

直接选择排序的思路也不难

在数组里面找到最大的元素和最小的元素,放到数组两端

下一趟排序选出次大的次小的元素,以此类推......

实现

我们需要两个指向两端元素的变量,begin和end这两变量存两端元素的下标

按照上面的思路我们需要判断什么时候不用排序,begin和end不是一直往中间靠嘛

那么当begin<end时就不用排序了

创建存最大/小值的下标的变量mini=begin,maxi=begin

然后遍历一遍该数组,如果某个元素大于maxi所指向的元素,那么把该下标赋给maxi

那么遍历一遍数组之后maxi就是最大值的下标了,mini同理

把下标为begin的元素和下标为mini的元素交换一下

由于最大值的下标可能是begin,这样交换完成后最大值会跑到之前最小值的位置了

所以需要进行下标修正

然后再交换下标为end和下标为maxi的元素

缩小范围++begin --end

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

2、堆排序

 堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是

通过堆来进行选择数据。需要注意的是排升序要建大堆,排降序建小堆。
堆排序的思路之前的博客讲过

用向下调整建堆,从最后一棵树倒着往前去调整,而叶子节点没有必要去调整

从倒数第一个非叶子节点开始,也就是最后一个节点的父节点(n-1-1)/2,一直走到根位置
void AdjustDown(int* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		// 找出小的那个孩子
		if (child + 1 < n && a[child + 1] > a[child])
		{
			++child;
		}


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


void HeapSort(int* a, int n)
{
    //建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
    //调整
	int end = n - 1;
    //只剩一个数就不用调整了
	while (end>0)
	{
        //0位置和最后一位交换
		Swap(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值