数据结构与算法学习(2)

#学习自用#

排序算法

冒泡排序优化

冒泡排序的本质是相邻两个元素比较大小,并交换顺序,完成一趟排序,可以把最大或者最小的数字放在序列的末尾,再进行下一趟的排序,一直进行到最后只剩两个元素进行比较。这其中可能会出现并不需要执行这么多次的情况,传统的冒泡一直会执行到最后一趟,执行了不必要的比较。

cpp 复制代码
void BubbleSort(int* a, int n)
{
	for (int j = n - 1; j >0 ; j--)
	{
		bool flag = true;
		for (int i = 0; i < j; i++)
		{
			if (a[i] > a[i + 1])
			{
				swap(a[i], a[i + 1]);
				flag = false;
			}
		}
		if (flag)
			break;
	}
	for (int i = 0; i < n; i++)
	{
		cout << a[i]<<' ';
	}
}

核心就是判断这一趟的排序中是否发生了位置交换,如果有那么说明排序后的数列可能还是无序的,如果没有交换,那么数列就已经是有序的了,无需再进行比较。

选择排序

选择排序的思想是,默认第一个数为最小值(也可以是最大,取决于你想升序还是降序排),再遍历之后的元素,去找比默认最小还要小的数,将其设置为最小数,完成第一次变量后再交换位置。第一个数排好后就用同样的方法排第二个数。与冒泡的区别是,选择排序最多调用元素个数-1次swab。

cpp 复制代码
void SelectSort(vector<int>& arr)
{
	for (int i = 0; i < arr.size() - 1; i++)
	{
		int minsubscript = i;
		for (int j = 1 + i; j < arr.size(); j++)
		{
			if (arr[j] < arr[minsubscript])
				minsubscript=j;
		}
		swap(arr[i], arr[minsubscript]);
	}
	for (int i : arr)
	{
		cout << i << ' ';
	}
}

插入排序

插入排序的思想是将数据分为有序区和无序区,无序区的数插入有序区,以升序为例,一个元素默认有序,如果第二个元素大于它那么有序区增加一个元素,如果比它小,就执行插入,确定要插入的位置,并把插入位以及有序区之后的元素后移,完成之后有序区增加一个元素,再向后遍历。

cpp 复制代码
void InsertSort(vector<int>& arr)
{
	for (int j = 1; j < arr.size(); j++)
	{
		for (int i = 0; i < j; i++)
		{
			
			if (arr[j] < arr[i])
			{
				swap(arr[j], arr[i]);
			}
		}
	}
	for (int i : arr)
		cout << i << ' ';
}

希尔排序

希尔排序是插入排序的一种,是插入的改进。设定步长,将数据进行分组然后使用插入排序,使数据出现局部有序,再缩短步长重复操作,直到设置步长为1,进行最后一次插入排序。

cpp 复制代码
void ShellSort(vector<int>& arr)
{
	for (int step = arr.size() / 2; step > 0; step /= 2)
	{
		for (int start = 0; start < step; start++)
			ShellInsert(arr, start, step);
	}
	for (int i : arr)
		cout << i << ' ';
}
void ShellInsert(vector<int>& arr, int start, int step)
{
	for(int j=start+step;j<arr.size();j+=step)
		for (int i = start; i < j; i += step)
		{
			if (arr[j] < arr[i])
				swap(arr[j], arr[i]);
		}
}

计数排序

计数排序是对排序元素具有一定要求才能使用的排序,如果排序元素全部为非负整数,那么我们可以遍历需要排序的元素找到最大值,设置一个计数数组,其中数组下标代表元素的值,该下标位置所对应的元素表示这个值存在多少个同样的数,这样我们只需要把排序元素遍历,存入相应的计数数组位置即可完成排序。

cpp 复制代码
void CountSort(vector<int>& arr)
{
	int Max = *max_element(arr.begin(), arr.end());//要添加头文件algorithm,返回最大值所在地址
	int* CountArr = new int[Max + 1];
	memset(CountArr, 0, sizeof(int) * (Max + 1));
	for (int i : arr)
		CountArr[i]++;
	for (int i = 0; i < Max + 1; i++)
	{
		while (CountArr[i])
		{
			cout << i << ' ';
			CountArr[i]--;
		}
	}
	delete[]CountArr;
}

计数数组记得初始化,否则会出现随机数,导致while(CountArr[i])进入死循环。

堆排序

通过构建大根堆,将最大值排在第一个父节点,再将最后一个节点与第一个父节点的值交换,结尾节点-1,重新构建大根堆,循环执行,即可完成升序排序。

cpp 复制代码
void BuidHeap(vector<int>& arr, int end)
{
	int Fnode = (end - 1) / 2;//本次排序最后一个父节点的下标
	for (; Fnode >= 0; Fnode--)
	{
		int Lnode = Fnode * 2 + 1;//左节点下标
		if (Lnode == end && arr[Lnode] > arr[Fnode])
			swap(arr[Lnode], arr[Fnode]);
		else if (Lnode < end && arr[Lnode] > arr[Lnode + 1]&& arr[Lnode] > arr[Fnode])
				swap(arr[Lnode], arr[Fnode]);
		if (Lnode < end && arr[Lnode] < arr[Lnode + 1])
		{
			Lnode += 1;
			if(arr[Lnode] > arr[Fnode])
				swap(arr[Lnode], arr[Fnode]);
		}
	}
	swap(arr[0], arr[end]);
}
void HeapSort(vector<int>& arr)
{
	for (int end = arr.size() - 1; end > 0; end--)//end 为本次排序的结尾下标
		BuidHeap(arr, end);
	for (int i : arr)
		cout << i << ' ';

}

快速排序

以第一个元素为基准,设置左端点和右端点,通过与基准比较,交换元素顺序,使左边的元素小于基准,右边的元素大于基准,使用递归实现数组升序排序。

cpp 复制代码
void FastSort(vector<int>& arr, int l, int r)
{
	if (l >= r)return;
	int i = l, j = r;
	int temp = arr[l];//store basement
	while (i < j)
	{
		while (arr[j] >= temp && i < j)j--;//这里必须限制i<j,否则j可能会减到i之后
		if (arr[j] < temp)
		{
			arr[i] = arr[j];
			i++;
		}
		while (arr[i] <= temp && i < j)i++;
		if (arr[i] > temp)
		{
			arr[j] = arr[i];
			j--;
		}
	}
	arr[i] = temp;
	FastSort(arr, l, i - 1);
	FastSort(arr, i+1, r);
}

这里运用了递归的思想,确定基准位置后, 用同样的步骤对基准左右两边进行排序,递归必须要确立一个终止条件,这里是 l>=r,l和r两个变量用于边界表示,不能改变,所以在遍历数据的时候使用了临时变量i,j

归并排序

归并排序是利用了递归的特性,在不断2分数组元素,直至每个小组元素只剩一个时,逐层退出递归并进行排序。

cpp 复制代码
void DivideMerge(vector<int>& arr, int l, int r)
{
	if (l == r)return;
	int mid = (l + r) / 2;
	DivideMerge(arr, l, mid );
	DivideMerge(arr, mid + 1, r);
	MergeSort(arr, l, r, mid);
}
void MergeSort(vector<int>& arr, int l, int r, int mid)
{
	int i = l, j = mid + 1;//分别指向合并的两个部分的开头,进行遍历
	int* temp = new int[r - l + 1];//用于存排好顺序后的数组,这里采用的是分别遍历比较大小,赋值给对应位置
	int index = 0;//临时数组下标
	while (i <= mid && j <= r)  
	{
		if (arr[i] < arr[j])
			temp[index++] = arr[i++];
		else
			temp[index++] = arr[j++];
	}	
	while(i <= mid)temp[index++] = arr[i++];//保证所有的数都放入的临时数组
	while (j <= r)temp[index++] = arr[j++];
	for (int i = 0; i < index; i++)
		arr[l + i] = temp[i];
	delete[]temp;
}

要注意,临时数组赋值给原来的数组时,原数组的下标为 l+i ,并非从 i 开始。

相关推荐
秃头佛爷44 分钟前
Python学习大纲总结及注意事项
开发语言·python·学习
浮生如梦_2 小时前
Halcon基于laws纹理特征的SVM分类
图像处理·人工智能·算法·支持向量机·计算机视觉·分类·视觉检测
dayouziei3 小时前
java的类加载机制的学习
java·学习
励志成为嵌入式工程师4 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
wheeldown5 小时前
【数据结构】选择排序
数据结构·算法·排序算法
青花瓷6 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
观音山保我别报错6 小时前
C语言扫雷小游戏
c语言·开发语言·算法