排序算法之时间复杂度O(n2)

排序算法是一个比较基础的问题,这里选择六个基础的排序算法进行介绍。从时间复杂度来进行介绍,其中选择排序,插入排序和冒泡排序的时间复杂度是 O ( n 2 ) O(n^2) O(n2)。堆排序,合并排序和快速排序的时间复杂度是 O ( n l o g 2 n ) O(nlog_{2}^{n}) O(nlog2n),这块内容会在下一个博客中介绍。
由于算法比较好理解,这里提供C++代码和简单的算法介绍。实力推荐一个手机APP动态展示算法,非常适合入门学习,上下班随时随地学习!
算法动态图解:链接:https://pan.baidu.com/s/1mX3s7VjLTKLr7MZhAQO-6Q
提取码:cv5y

选择排序

选择排序就是从重复“从待排序的序列数据中选出最小值”,将其与待排序序列的最左边的数字进行交换。在待排序的序列中选择出最小值采用的是线性查找。

void ChooseSort()
{
	int list[10] = { 6,3,4,8,9,1,2,3,5,9 };
	int len = 10;
	int min_index = 0;
	int temp;
	for (size_t i = 0; i < len; i++)
	{
		min_index = i;
		for (size_t j = i; j < len-1; j++)
		{
			if (list[min_index] > list[j + 1])
			{
				min_index = j + 1;
			}
		}
		//将最小值放到最左侧
		temp = list[i];
		list[i] = list[min_index];
		list[min_index] = temp;
	}
	for (int i = 0; i <len; i++)
	{
		cout << list[i]<<",";
	}
	cout << endl;
}

因为选择排序中使用了线性查找来寻找最小值,因此在第一轮的时候需要比较n-1个数字。第二轮需要n-2个数字…到n-1时只需要比较1个数字。比较次数总共为(n-1)+(n-2)+…+1 ≈ \approx n 2 2 \frac{n^2}{2} 2n2,时间复杂度 O ( n 2 ) O(n^2) O(n2)

冒泡排序

顾名思义,如同算法的名称一样。我们每一轮都将该待排序的序列中最大的数字放到待排序数组的最右侧。从数组的左侧开始依次比较相邻的两个数字,如果左侧的数字比右侧大则进行交换。

void BubbleSort()
{
	int list[10] = { 6,3,4,8,9,1,2,3,5,9 };
	int len = 10;
	int temp;
	for (size_t i = 0; i < len-1; i++)//n个元素需要比较n-1次(n-1趟)
	{
		//每一趟都会选择一个最大值放到数组右侧,已经排序好的不需要再检索
		for (size_t j = 0; j < len-i-1; j++)
		{
			if (list[j] > list[j + 1])
			{
				//交换
				temp = list[j];
				list[j] = list[j+1];
				list[j+1] = temp;
			}
		}
	}
	for (int i = 0; i < len; i++)
	{
		cout << list[i] << ",";
	}
	cout << endl;
}

每一轮都要进行一次线性比较,所以总的比较次数与选择排序一致。(n-1)+(n-2)+…+1 ≈ \approx n 2 2 \frac{n^2}{2} 2n2,时间复杂度 O ( n 2 ) O(n^2) O(n2)

插入排序

不断的从数组的未排序区域中取出元素,与左侧已归位的数字进行比较。若左侧数字更大,则进行交换。重复该操作,直到左边已归位的的数字比取出的数字更小,或者取出的数字已经移到整个序列的最左边为止。

  • 第一轮操作时,默认数组第一个元素已经排序完成。
void InsertSort()
{
	int list[10] = { 6,3,4,8,9,1,2,3,5,9 };
	int len = 10;
	int temp;
	for (size_t i = 1; i < len; i++)
	{
		//将右侧待排序的第一个元素与左侧排序好的元素依次进行比较,限制j>0取到序列的最左侧
		for (size_t j = i; j > 0; j--)
		{
			if (list[j] < list[j - 1])
			{
				temp = list[j];
				list[j] = list[j - 1];
				list[j - 1] = temp;
			}
		}
	}
	for (int i = 0; i < len; i++)
	{
		cout << list[i] << ",";
	}
	cout << endl;
}

这里的时间复杂度按照最糟糕的情况计算,如果取出来的数字比左边已归位的数字都要小。那第2轮要比较1次,第3轮比较2次…最后一轮比较n-1次。所以总的比较次数为(n-1)+(n-2)+…+1 ≈ \approx n 2 2 \frac{n^2}{2} 2n2,时间复杂度 O ( n 2 ) O(n^2) O(n2)
附部分APP截图
ALT
在这里插入图片描述
不是推广APP,是真的很好用(笑哭)。APP链接有问题联系QQ:1678354579
以上代码全部手敲,经过完整测试无误;

<think>嗯,用户现在问的是C语言中的排序算法及其时间复杂度。首先,我需要回忆一下常见的排序算法有哪些,比如冒泡排序、选择排序、插入排序、快速排序、归并排序、堆排序这些。然后,针对每个算法,我需要解释它们的原理、时间复杂度以及用C语言实现的例子。 用户可能是在学习数据结构和算法,或者在准备面试,需要了解不同排序算法的优劣。他们的深层需求可能是如何在实际编程中选择合适的排序算法,以及理解不同算法之间的性能差异。 首先,我应该列举每个算法的基本思想,然后给出时间复杂度,最好情况、最坏情况和平均情况。同时,可能需要提到空间复杂度,尤其是对于需要额外空间的算法如归并排序。另外,稳定性也是一个重要点,比如哪些算法是稳定的,哪些不是。 接下来,用户可能需要代码示例,这样他们可以理解如何在C语言中实现这些算法。但要注意代码的正确性,比如快速排序的分区处理,避免死循环或者错误的分割。此外,需要确保示例代码简洁,便于理解。 另外,用户可能对时间复杂度不太熟悉,所以需要用简单易懂的语言解释什么是O(n&sup2;)和O(n log n),以及为什么快速排序通常比归并排序快,尽管它们的时间复杂度相同。 还要考虑实际应用中的情况,比如数据量小的时候插入排序可能更高效,或者快速排序在处理大量数据时的优势,但最坏情况下的性能问题,如何通过随机化选择枢轴来避免。 最后,是否需要比较这些算法,给出在不同场景下的选择建议。比如,当内存有限时,堆排序可能更合适,因为它是原地排序;而当需要稳定性时,归并排序更好。 需要确保回答结构清晰,分点列出每个算法,包含原理、时间复杂度、C代码示例以及优缺点。同时,要使用正确的中文术语,避免技术术语的错误翻译。可能还需要检查代码是否存在语法错误,比如忘记分号或者括号不匹配的问题。 总结一下,回答应该覆盖主要排序算法,每个算法的详细解释,时间复杂度的比较,以及适用场景,帮助用户全面理解并能在实际中应用。</think>以下是 C 语言中常见排序算法及其时间复杂度的详细介绍,包含原理说明、代码示例和适用场景分析。 --- ### **1. 冒泡排序(Bubble Sort)** - **原理** 依次比较相邻元素,将较大的元素交换到右侧,每轮将当前最大元素“冒泡”到末尾。 - **时间复杂度** - 最好情况:$O(n)$(已有序时,通过优化可提前终止) - 平均和最坏情况:$O(n^2)$ - **稳定性**:稳定 - **代码示例**: ```c void bubbleSort(int arr[], int n) { for (int i = 0; i < n-1; i++) { int swapped = 0; // 优化:若未交换则提前结束 for (int j = 0; j < n-i-1; j++) { if (arr[j] > arr[j+1]) { int temp = arr[j]; arr[j] = arr[j+1]; arr[j+1] = temp; swapped = 1; } } if (!swapped) break; } } ``` - **适用场景**:数据量小或基本有序时效率较高。 --- ### **2. 选择排序(Selection Sort)** - **原理** 每次从未排序部分选择最小(或最大)元素,与未排序部分的第一个元素交换。 - **时间复杂度** - 所有情况:$O(n^2)$ - **稳定性**:不稳定(可能破坏原顺序) - **代码示例**: ```c void selectionSort(int arr[], int n) { for (int i = 0; i < n-1; i++) { int min_idx = i; for (int j = i+1; j < n; j++) { if (arr[j] < arr[min_idx]) { min_idx = j; } } int temp = arr[i]; arr[i] = arr[min_idx]; arr[min_idx] = temp; } } ``` - **适用场景**:简单但效率低,适用于数据量极小且交换成本高的场景。 --- ### **3. 插入排序(Insertion Sort)** - **原理** 将未排序元素逐个插入到已排序部分的正确位置。 - **时间复杂度** - 最好情况:$O(n)$(已有序) - 平均和最坏情况:$O(n^2)$ - **稳定性**:稳定 - **代码示例**: ```c void insertionSort(int arr[], int n) { for (int i = 1; i < n; i++) { int key = arr[i]; int j = i-1; while (j >= 0 && arr[j] > key) { arr[j+1] = arr[j]; j--; } arr[j+1] = key; } } ``` - **适用场景**:小规模数据或基本有序时高效,常用于优化其他算法(如快速排序的子过程)。 --- ### **4. 快速排序(Quick Sort)** - **原理** 分治法:选择一个基准元素(pivot),将数组分为小于基准和大于基准的两部分,递归排序子数组。 - **时间复杂度** - 最好和平均情况:$O(n \log n)$ - 最坏情况:$O(n^2)$(当数组已有序且基准选择不当时) - **稳定性**:不稳定 - **代码示例**: ```c int partition(int arr[], int low, int high) { int pivot = arr[high]; // 选择最后一个元素为基准 int i = low - 1; for (int j = low; j <= high-1; j++) { if (arr[j] < pivot) { i++; int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } // 将基准放到正确位置 int temp = arr[i+1]; arr[i+1] = arr[high]; arr[high] = temp; return i+1; } void quickSort(int arr[], int low, int high) { if (low < high) { int pi = partition(arr, low, high); quickSort(arr, low, pi-1); quickSort(arr, pi+1, high); } } ``` - **优化**:随机选择基准(避免最坏情况) - **适用场景**:大规模数据,实际应用中速度最快。 --- ### **5. 归并排序(Merge Sort)** - **原理** 分治法:将数组分成两半,分别排序后合并。 - **时间复杂度** - 所有情况:$O(n \log n)$ - **稳定性**:稳定 - **空间复杂度**:$O(n)$(需要额外空间合并) - **代码示例**: ```c void merge(int arr[], int left, int mid, int right) { int n1 = mid - left + 1; int n2 = right - mid; int L[n1], R[n2]; for (int i = 0; i < n1; i++) L[i] = arr[left + i]; for (int j = 0; j < n2; j++) R[j] = arr[mid + 1 + j]; int i = 0, j = 0, k = left; while (i < n1 && j < n2) { if (L[i] <= R[j]) arr[k++] = L[i++]; else arr[k++] = R[j++]; } while (i < n1) arr[k++] = L[i++]; while (j < n2) arr[k++] = R[j++]; } void mergeSort(int arr[], int left, int right) { if (left < right) { int mid = left + (right - left) / 2; mergeSort(arr, left, mid); mergeSort(arr, mid+1, right); merge(arr, left, mid, right); } } ``` - **适用场景**:需要稳定排序且内存充足时。 --- ### **6. 堆排序(Heap Sort)** - **原理** 将数组视为完全二叉树,构建最大堆(或最小堆),反复取出堆顶元素并调整堆。 - **时间复杂度** - 所有情况:$O(n \log n)$ - **稳定性**:不稳定 - **空间复杂度**:$O(1)$(原地排序) - **代码示例**: ```c void heapify(int arr[], int n, int i) { int largest = i; int left = 2*i + 1; int right = 2*i + 2; if (left < n && arr[left] > arr[largest]) largest = left; if (right < n && arr[right] > arr[largest]) largest = right; if (largest != i) { int temp = arr[i]; arr[i] = arr[largest]; arr[largest] = temp; heapify(arr, n, largest); } } void heapSort(int arr[], int n) { // 构建最大堆 for (int i = n/2 - 1; i >= 0; i--) heapify(arr, n, i); // 逐个取出堆顶元素 for (int i = n-1; i >= 0; i--) { int temp = arr[0]; arr[0] = arr[i]; arr[i] = temp; heapify(arr, i, 0); } } ``` - **适用场景**:内存受限且需要稳定 $O(n \log n)$ 时间。 --- ### **总结对比** | 算法 | 平均时间复杂度 | 稳定性 | 空间复杂度 | 适用场景 | |---------------|----------------|--------|--------------|----------------------------| | 冒泡排序 | $O(n^2)$ | 稳定 | $O(1)$ | 小数据或已部分有序 | | 选择排序 | $O(n^2)$ | 不稳定 | $O(1)$ | 简单实现,交换成本高时 | | 插入排序 | $O(n^2)$ | 稳定 | $O(1)$ | 小数据或链表结构 | | 快速排序 | $O(n \log n)$ | 不稳定 | $O(\log n)$ | 通用场景,大规模数据 | | 归并排序 | $O(n \log n)$ | 稳定 | $O(n)$ | 稳定排序,需要额外内存 | | 堆排序 | $O(n \log n)$ | 不稳定 | $O(1)$ | 内存受限且需稳定时间复杂度 | --- ### **如何选择算法?** 1. **数据规模小**(如 $n \le 100$):插入排序或冒泡排序。 2. **需要稳定性**:归并排序。 3. **内存充足且追求速度**:快速排序(需优化基准选择)。 4. **内存有限**:堆排序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值