本文主要讲解几种数据结构当中常用的排序算法,本文的例子与代码都以实现升序排序为基础。
首先,给出实现排序的基础交换两个值的函数
void Swap(int* a, int* b)//交换函数 { int ret = *a; *a = *b; *b = ret; return; }
一. 冒泡排序
1. 实现思想:
遍历要排序的数列,每次遍历都要依次进行比较,一次比较两个元素,如果顺序与预期不一致,就进行交换,直至一次遍历结束。
2. 时间复杂度:O(n^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定排序
5. 实现代码:
//1.冒泡排序:从后向前冒,每次把最小的放在前面 void BubbleSort(int arr[], size_t size) { if(size <= 1)//元素个数不用排序 return; //[0,bound)表示有序元素,[bound,size)表示待排序元素 size_t bound = 0; for(; bound<size; ++bound) { size_t cur = size - 1; for(; cur>bound; --cur) { if(arr[cur] < arr[cur-1]) Swap(&arr[cur], &arr[cur-1]); } } }
二. 选择排序
1. 实现思想
以升序为例,遍历要排序的数列,每次遍历找出最小的元素将其与此次遍历到的元素交换,遍历结束,排序也结束。
2. 时间复杂度:O(n^2)
3. 空间复杂度:O(1)
4. 稳定性:不稳定排序
5. 实现代码:
//2.选择排序:升序 void SelectSort(int arr[], size_t size) { if(size <= 1) return; size_t bound = 0; for(; bound<size; ++bound) { size_t cur = bound + 1; for(; cur < size; ++cur) { if(arr[bound] > arr[cur]) Swap(&arr[bound], &arr[cur]); } } }
三. 插入排序
1. 实现思想
把有序区间(从一个元素开始)当作线性表,依次遍历待排序元素,寻找合适位置,插入到有序区间中。
2. 时间复杂度:O(n^2)
3. 空间复杂度:O(1)
4. 稳定性:稳定排序
5. 实现代码:
//3.插入排序 //把有序区间当作线性表,把bound所指元素插入到这个有序的线性表中 void InsertSort(int arr[], size_t size) { if(size <= 1) return; //1.定义好边界,[0,bound)表示有序的线性表 size_t bound = 1; for(; bound<size; ++bound) { //2.保存当前bound所指的元素 int value = arr[bound];//保存是为了搬运 size_t cur = bound;//cur辅助搬运, //3.从后向前去找一个合适的放置当前bound所指的元素 for(; cur>0; --cur) { if(arr[cur-1] > value)//搬运 { arr[cur] = arr[cur-1]; } else//已找到合适位置 break; } //该句不能写在else控制的语句中 //因为若当前bound所指元素前面所以有都比它大,此时要插入到数组的第一个元素,但是此时循环结束了,执行不了该句赋值 arr[cur] = value;//插入bound所指元素 } }
四. 堆排序
1. 实现思想
在之前介绍堆的相关操作实现博客中,介绍过堆排序的思想。这里实现思想是一样的,就是依次删除堆顶元素,直至删完,排序结束。不够要注意的是:大堆实现升序,小堆实现降序。
2. 时间复杂度:O(n*logn)
3. 空间复杂度:O(1)
4. 稳定性:不稳定排序
5. 实现代码:
这里有上浮式调整和下沉式调整两种,具体意义可见上面贴的堆的实现博客。
//4.堆排序 void AdjustDown(int arr[], size_t size, size_t index)//下沉式调整函数 { size_t parent = index; size_t child = 2*parent + 1; while(child < size) {//建大堆 if(child+1 < size && arr[child] < arr[child+1]) child = child + 1; //此时child指向左右子树较大的一个 if(arr[parent] < arr[child]) Swap(&arr[parent], &arr[child]); parent = child; child = 2*parent + 1; } } void AdjustUp(int arr[], size_t size, size_t index)//上浮式调整函数 { size_t child = index; size_t parent = (child-1)/2; while(child > 0) {//建大堆 if(arr[parent] < arr[child]) Swap(&arr[parent], &arr[child]); child = parent; parent = (child-1)/2; } } void HeapCreate(int arr[], size_t size)//创建堆 {//大堆->升序 if(size <= 1) return; //下沉式调整 //从后向前遍历,起始位置是堆从后向前第一个非叶子结点 size_t i = (size-1-1)/2;//最后一个元素的父结点就是起始位置 for(; i>0; --i) { AdjustDown(arr, size, i); } AdjustDown(arr, size, 0); //上浮式调整 //从前向后遍历,起始位置为数组首元素 //for(i=0; i<size;++i) //{ // AdjustUp(arr, i+1,i); //} } void HeapPop(int arr[], size_t size)//删除堆顶元素 { if(size <= 1) return; Swap(&arr[0], &arr[size-1]); AdjustDown(arr, size-1, 0); } void HeapSort(int arr[], size_t size)//堆排序 { if(size <= 1) return; //创建堆 HeapCreate(arr, size); //循环删除堆顶元素 size_t i = 0; for(; i<size; ++i) { HeapPop(arr, size-i);//第二个参数表示当前数组中哪部分满足堆的规则 //第一次删除前,[0,size)是堆; //第二次删除前,[0,size-1)是堆; //第三次删除前,[0,size-2)是堆... } return; }