数据结构经典算法

本文详细介绍了数据结构中的排序算法,包括直接插入排序、希尔排序、选择排序、堆排序、冒泡排序、快速排序和归并排序。每种排序算法都阐述了基本思想、代码实现、时间复杂度和空间复杂度,以及稳定性分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在学习数据结构中我感觉最重要的就是排序算法,以下我列举最常见的几种排序算法及他们各自的思想
在这里插入图片描述
接下来我一一介绍这几种排序算法:
1.直接插入排序
直接插入排序是一种简单的插入排序法,其基本思想是:把待排序的记录按其关键码值的大小逐个插入到一
个已经排好序的有序序列中,直到所有的记录插入完为止,得到一个新的有序序列 。
代码实现:

void InsertSort(int *array, int size)
{
	//直接从第二个元素开始,因为第一个元素已经有序
	for (int i = 1; i < size; i++)
	{
		int key = array[i];//key为待排元素
		int end = i - 1;//end为比较元素
		while (end >= 0 && key < array[end])
		{
			array[end + 1] = array[end];//把end位置的元素放到end+1的位置
			end--;
		}
		array[end + 1] = key;
	}
}

性质:

  1. 适用场景:元素接近有序,直接插入排序算法的时间效率越高
  2. 空间复杂度:o(n^2)
  3. 时间复杂度:o(1)
  4. 稳定性:稳定

2.希尔排序
希尔排序(Shell Sort)是插入排序的一种。是针对直接插入排序算法的改进。该方法又称缩小增量排序,因D.L.Shell于1959年提出而得名。希尔排序法的基本思想是:选定一个整数,把待排序文件中所有元素按整数的间隔分组,并对每一组内的记录进行排序。然后,取,重复上述分组和排序的工作。每次整数减一,当整数到达1时,这时所有记录在统一组内排好序。
代码实现:

void ShellSort(int *array, int size)
{
	//gap作为分组的依据
	int gap = size;
	while (gap > 1)
	{
		gap = gap / 3 + 1;//
		for (int i = gap; i < size; i++)
		{
			int key = array[i];
			int end = i - gap;
			while (end >= 0 && key < array[end])
			{
				array[end + gap] = array[end];
				end -= gap;
			}
			array[end + gap] = key;
		}
	}
}

性质:

  1. 希尔排序是对直接插入排序的优化。
  2. 当gap > 1时都是预排序,目的是让数组更接近于有序。当gap == 1时,数组已经接近有序的了,这样就
    会很快。这样整体而言,可以达到优化的效果。我们实现后可以进行性能测试的对比。
  3. 平均时间复杂度: O(N1.3—N2)
  4. 空间复杂度:o(n)
  5. 稳定性:不稳定

3.选择排序
选择排序的基本思想是:每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,用剩余的数据和其相比较,每次找到数据中最大或者最小的元素,循环进行直到全部待排序的数据元素排完 。
代码实现:

void SelectSort(int *array, int size)
{
	for (int i = 0; i < size-1; i++)//控制循环的趟数
	{
		int maxpos = 0;
		for (int j = 1; j < size - i; j++)
		{
			if (array[j] > array[maxpos])//比较j和maxpos位置元素的大小,看maxpos位置是否合适
			{
				maxpos = j;
			}
		}
		if (maxpos != size - 1 - i)//第一次比较结束时,判断maxpos是不是在末尾
		{
			swap(&array[maxpos], &array[size - 1 - i]);
		}
	}
}

性质:

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

上边的选择排序:一次只可以找出一个最大或者最小的元素,效率比较低,所以对选择排序进行优化,代码如下:

//选择排序的优化
void SelectSortMax(int *array, int size)
{
	int begin = 0;
	int end = size - 1;//找到元素的起始和末尾
	while (begin < end)
	{
		int minpos = begin;
		int maxpos = begin;
		int indx = begin + 1;//待比较元素
		while(index <= end)
		{
			if (array[index]>array[maxpos])//如果待比较元素比最大的元素还大,说明最大的元素位置不合理
			{
				maxpos = index;
			}
			if (array[index] < array[minpos])//如果待比较元素比最小的元素还小,说明最小的元素位置不合理
			{
				minpos = index;
			}
			index++;
		}
		if (maxpos != end)//比较结束后判断maxpox位置是否在末尾
		{
			swap(&array[maxpos], &array[end]);
		}
		if (minpos == end)//这行代码是判断最小元素的位置是不是在队列末尾,如果在,不能直接进行交换,因为上一步放置maxpos位置时已经进行交换,所以我们先把minpos的位置放在上一步交换之前maxpos的位置,然后再判断minpos的位置是否在起始
		{
			minpos = maxpos;
		}
		if (minpos != begin)//比较结束后判断minpos位置是否在起始
		{
			swap(&array[minpos], &array[begin]);
		}
		begin++;
		end--;
	}
}

4.堆排序
堆排序是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。它是通过堆来进行选择数据。注意:排升序要建大堆,排降序建小堆。
代码实现:

void HeapSort(int *array, int size)
{
	//使用堆排序首先要进行建堆
	//root为(size-2)>>1的原因:
	//size-1就是队列中最后一个元素的位置,也就是最后一个孩子的位置
	//在二叉树中:child = parent*2 +1;所以最后一个父节点位置:(child-1)>>1
	for (int root = (size - 2) >> 1; root >= 0; root--)
	{
		AdjustHeap(array, size, root);
	}
	//开始排序
	int end = size - 1;
	while (end)
	{
		//因为升序建立的是大堆,所以将堆顶元素直接和堆底元素进行交换
		int temp = array[0];
		array[0] = array[end];
		array[end] = temp;

		//交换完毕后,调整现在的堆顶元素
		AdjustHeap(array, end, 0);

		end--;
	}
}

void AdjustHeap(int *array, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (child + 1 < size && array[child] < array[child + 1])
		{
			child += 1;
		}
		if (array[child] > array[parent])
		{
			int temp = array[parent];
			array[parent] = array[child];
			array[child] = temp;

			parent = child;
			child = parent * 2 + 1;
		}
		else
			return;
	}
}

性质:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(1)
  3. 稳定性:不稳定

5.冒泡排序
冒泡排序的基本思想是:对所有相邻记录的关键字值进行比效,每次找到数列中一个最大或者最小的元素。
代码实现:

void BubbleSort(int *array, int size)
{
	//n个元素只用比较n-1次,最后剩下一个元素不用进行比较
	for (int i = 0; i < size - 1; i++)
	{
		//j < size-i-1理解:
		//j < size-i:待比较元素,没比较一次就有一个元素已经有序
		//j < size-i-1:因为j是和j+1进行比较的,所以还要保证j+1合法
		for (int j = 0; j < size - i - 1; j++)
		{
			if (array[j] > array[j + 1])
			{
				int temp = array[j];
				array[j] = array[j + 1];
				array[j + 1] = temp;
			}
		}
	}
}

性质:

  1. 时间复杂度:O(N^2)
  2. 空间复杂度:O(1)
  3. 稳定性:稳定

6.快速排序
快速排序是这几种算法中比较难理解的一种,快速排序是Hoare于1962年提出的一种二叉树结构的交换排序方法,其**基本思想是:**任取待排序元素序列中的某元素作为基准值(一般取末尾元素),按照该排序码将待排序集合分割成两子序列,从左往右找基准值大的,从右往左找比基准值小的,进行交换,交换完成后,左子序列中所有元素均小于基准值,右子序列中所有元素均大于基准值,然后最左右子序列重复该过程,直到所有元素都排列在相应位置上为止。
快速排序有三种实现方法:1.1. hoare版本 2. 挖坑法 3. 前后指针版本;以下我在代码中讲解这三种方法:

//三数取中法
int GetMid(int *array, int left, int right)
{
	int mid = left + (right - left) >> 1;
	//首先判断left和right-1位置的元素的大小
	//然后再用mid位置元素的大小和他们相比较,找出三数中间位置的元素
	if (array[left] < array[right - 1])
	{
		if (array[mid] < array[left])
			return left;
		else if (array[mid] > array[right - 1])
			return right - 1;
		else
			return mid;
	}
	else
	{
		if (array[mid] < array[right - 1])
			return right - 1;
		else if (array[mid]>array[left])
			return left;
		else
			return mid;
	}
}
//1.hoare版本:
int Partion1(int *array, int left, int right)
{
	int begin = left;
	int end = right - 1;
	//三数取中是为了优化最差情况
	//最差情况:会存在一种情况,每次划分数据都存在在基准值一侧,所以我们采用三数取中来尽量避免
	int mid = GetMid(array, left, right);
	swap(&array[mid], &array[right - 1]);
	int key = array[end];//这时候的key就是偏向中间的数

	while (begin < end)
	{
		//从左往右找比基准值大的
		while (begin < end && array[begin] <= key)
		{
			begin++;
		}
		//从右往左找比基准值小的
		while (begin < end && array[end] >= key)
		{
			end--;
		}
		if (begin < end)
		{
			swap(&array[begin], &array[end]);
		}
	}
	if (begin != right - 1)
	{
		swap(&array[begin], &array[right-1]);
	}
	return begin;
}

//2.挖坑法
//此方法就是通过begin和end位置的元素不断交换来找到基准值的位置
void Partion2(int *array, int left, int right)
{
	int begin = left;
	int end = right - 1;
	int mid = GetMid(array, left, right);
	swap(&array[mid], &array[right - 1]);
	int key = array[end];
	while (begin < end)
	{
		//从左往右找比基准值大的
		while (begin < end && array[begin] <= key)
		{
			begin++;
		}
		//找到之后,就把begin位置的元素放置到end的位置,同时end向中间走一步
		if (begin < end)
			array[end--] = array[begin];
		//从右往左找比基准值小的
		while (begin < end && array[right] >= key)
		{
			end--;
		}
		//找到之后,就把end位置的元素放置到begin位置,同时begin向中间走一步
		if (begin < end)
			array[begin++] = array[end];
	}
	//最后跳出循环,begin和end相遇,也就是基准值的位置
	array[begin] = key;
	return begin;
}

//3.前后指针法
void Partion3(int *array, int left, int right)
{
	int cur = 0;
	int prev = cur - 1;
	int mid = GetMid(array, left, right);
	swap(&array[mid], &array[right - 1]);
	int key = array[right - 1];

	while (cur < right)
	{
		//if条件不满足,只给cur++;这时cur和prev就不是前后指针关系
		//当if条件满足,则证明array[cur]<key,就说明找到了第一个比基准值小的元素
		//这时候就把prev和cur位置的元素交换,将小的元素放在左边
		if (array[cur] < key && ++prev != cur)
		{
			swap(&array[prev], &array[cur]);
		}
		cur++;
	}
	//判断prev的下一步是不是在末尾,如果不是就把基准值放置在prev位置
	if (++prev != right - 1)
	{
		swap(&array[prev], &array[right - 1]);
	}
	return prev;
}

//快速排序
void QuickSort(int *array, int left ,int right)
{
	if (right <= left)
	{
		return;
	}
	else
	{
		//区间是左闭右开
		int div = Partion1(array, left, right);

		Quick(array, left, div);
		Quick(array, div + 1, right);
	}
}

性质:

  1. 时间复杂度:O(N*logN)
  2. 空间复杂度:O(logN)
  3. 稳定性:不稳定

7.归并排序
归并排序的**基本思想是:**归并排序(MegreSort)是建立在归并操作上的一种有效的排序算法,该算法是采用分治法(Divide andConquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
代码实现:

void MegreData(int *array, int *temp, int left, int mid, int right)
{
	//区间是左闭右开的
	int begin1 = left, end1 = mid;
	int begin2 = mid, den2 = right;
	int index = left;
	//将begin1和begin2区间中的元素放置到新空间中
	while (begin1 < end1 && begin2 < end2)
	{
		if (array[begin1] <= array[begin2])
			temp[index++] = array[begin1++];
		else
			temp[index++] = array[begin2++];
	}
	//证明begin2中的元素已经放置完毕
	while (begin1<end1)
	{
		temp[index++] = array[begin1++];
	}
	//证明begin1中的元素已经放置完毕
	while (begin2<end2)
	{
		temp[index++] = array[begin2++];
	}
}

void _MegreSort(int *array, int *temp, int left, int right)
{
	if (right - left>1)
	{
		int mid = left + (right - left) >> 1;
		_MegreSort(array, temp, left, mid);
		_MegreSort(array, temp, mid, right);
		//合并数据
		MegreData(array, temp, left, mid, right);
		//加left的原因是,每次都要往新空间中去拷贝,并且每次拷贝的元素都不是从头开始,因为有递归存在
		memcpy(array + left, temp + left, (right - left)*sizeof(array[0]));
	}
}

void MegreSort(int *array, int size)
{
	int temp = (int*)malloc(sizeof(array[0])*size);
	if (temp == NULL)
	{
		assert(0);
		return;
	}
	_MegreSort(array, temp, 0, size);
	free(temp);
}

性质:

  1. 归并的缺点在于需要O(N)的空间复杂度,归并排序的思考更多的是解决在磁盘中的外排序问题。
  2. 时间复杂度:O(N*logN)
  3. 空间复杂度:O(N)
  4. 稳定性:稳定
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值