博客
关于我
排序算法总结——c++实现
阅读量:300 次
发布时间:2019-03-03

本文共 11140 字,大约阅读时间需要 37 分钟。

 

Java实现见链接:

 

 

排序算法的稳定性分析

 

选择排序无法保证稳定性

 

归并排序可以保证稳定性:(相等的就先放置左区域内的元素)

 

快速排序无法保持稳定性(因为partition的时候无法保持稳定性)

以后补充

 

 

堆排序无法保证稳定性:(图中数组建堆的过程,稳定性就会被破坏,第二个4会跑到第三个4的后面)

 

 

 

稳定性的意义在哪

数据不会乱

 

 

 

冒泡——稳定排序算法

时间复杂度 O(n*n),空间复杂度O(n)。

下面代码中交换两个数的值的方式虽然节省了一个变量,但是如果溢出就不好玩了,所以不要轻易乱秀。

void bubble(int nums[]) {		if (nums == nullptr) return;		//获取数组长度		int len= sizeof(nums) / sizeof(int);		for (int end = len - 1; end >0; end++) {			for (int i=0; i < end; i++) {				if (nums[i] > nums[i+1]) {					nums[i] = nums[i] + nums[i + 1];					nums[i + 1] = nums[i] - nums[i + 1];					nums[i] = nums[i] - nums[i + 1];				}			}		}	}

 

冒泡排序改进版——鸡尾酒排序

#include 
// 分类 -------------- 内部比较排序// 数据结构 ---------- 数组// 最差时间复杂度 ---- O(n^2)// 最优时间复杂度 ---- 如果序列在一开始已经大部分排序过的话,会接近O(n)// 平均时间复杂度 ---- O(n^2)// 所需辅助空间 ------ O(1)// 稳定性 ------------ 稳定void Swap(int A[], int i, int j){ int temp = A[i]; A[i] = A[j]; A[j] = temp;}void CocktailSort(int A[], int n){ int left = 0; // 初始化边界 int right = n - 1; while (left < right) { for (int i = left; i < right; i++) // 前半轮,将最大元素放到后面 { if (A[i] > A[i + 1]) { Swap(A, i, i + 1); } } right--; for (int i = right; i > left; i--) // 后半轮,将最小元素放到前面 { if (A[i - 1] > A[i]) { Swap(A, i - 1, i); } } left++; }}int main(){ int A[] = { 6, 5, 3, 1, 8, 7, 2, 4 }; // 从小到大定向冒泡排序 int n = sizeof(A) / sizeof(int); CocktailSort(A, n); printf("鸡尾酒排序结果:"); for (int i = 0; i < n; i++) { printf("%d ", A[i]); } printf("\n"); return 0;}

 

 

 

插入排序——稳定排序

最差时间复杂度:O(n^2)

最优时间复杂度:O(n)
平均时间复杂度:O(n^2)

 

当存储容器为数组时

//插入排序	void insert(int nums[]) {		if (nums == nullptr) return;		//获取数组长度		int len = sizeof(nums) / sizeof(int);		//当前考察i位置的数字		for (int i = 1; i < len; i++) {			//从i往前比较,每次与j和j+1的位置相比较。如果j+1的值小,则把小的放在前面			for (int j = i - 1; j >= 0 && nums[j] > nums[j + 1]; j--) {				nums[j] = nums[j] + nums[j + 1];				nums[j + 1] = nums[j] - nums[j + 1];				nums[j] = nums[j] - nums[j + 1];			}		}	}

 

当容器为vector时

#include 
#include
using namespace std;//传入一对迭代器,标准的插入排序void Insertion_sort(vector
::iterator begin, vector
::iterator end){ for (auto p1 = begin; p1 != end; ++p1) { auto p2 = p1; while ((p2 != begin) && *(p2 - 1) > *p2) { int temp = *(p2); *p2 = *(p2 - 1); *(p2 - 1) = temp; --p2; } }}int main(int argc, char **argv){ int a[10] = { 2,5,6,3,1,4,8,7,9,0 }; vector
vec(a, a + 10); Insertion_sort(vec.begin(), vec.end()); for (size_t i = 0; i < vec.size(); ++i) { cout << vec[i] << " "; } return 0;}

 

当容器为链表时,从前往后遍历,找到对应的位置,进行链表的插入操作即可。

预备知识,链表的创建

#include
using namespace std;struct ListNode{ int val; struct ListNode *next;};//创建链表ListNode* create(int arr[], int n){ if (arr == NULL || n <= 0) return NULL; ListNode *head = (ListNode*)malloc(sizeof(struct ListNode)); head->val = arr[0]; head->next = NULL; ListNode *tmp = head; ListNode *next = NULL; for (int i = 1; i < n; i++) { next = (ListNode*)malloc(sizeof(struct ListNode)); if (next) { next->val = arr[i]; next->next = NULL; tmp->next = next; tmp = next; } } return head;}int main() { int arr[6] = { 2,4,5,6,8,9 }; ListNode*first=create(arr, 6); return 0;}

 

链表实现插入排序代码(硬着头皮,终于写出来了。再坚持一下,再坚持一下,就会成功)

#include 
#include
using namespace std;struct linklist { int data; linklist* next; linklist(int num = 0) : data(num), next(nullptr) {}};//用了两个指针,fast指针用于比较当前节点的值与数组中的值的大小//slow指向fast所指节点的前一个节点,slow用于插入新节点linklist* insertion_sort(int arr[], int len){ linklist *before = (linklist*)malloc(sizeof(struct linklist)); linklist *head = (linklist*)malloc(sizeof(struct linklist)); head->data = arr[0]; before->next = head; head->next = NULL; linklist *fast = head; linklist *slow = before; linklist *next = NULL; for (int i = 1; i < len; i++) { //创建链表节点 next = (linklist*)malloc(sizeof(struct linklist)); next->data = arr[i]; //向后查找比arr[i]大的链表节点的位置 while ((arr[i] > fast->data)&&(fast->next!=nullptr)) { fast = fast->next; slow = slow->next; } //此时说明fast并没有指向链表最后一个元素 if (fast->next!=nullptr) { //说明arr[i]的值是最小的,应该插到链表头 if (slow==before) { next->next = slow->next; slow->next = next; } else { next->next = fast; slow->next = next; } } //此时fast指向链表结尾,需要比较fast->data与arr[i]的值 else { if (arr[i] > fast->data) { fast->next = next; next->next = nullptr; } else { next->next = fast; slow->next = next; } } slow = before; fast = slow->next; } return slow->next;}int main(int argc, char **argv){ int a[10] = { 2,5,6,3,1,4,8,7,9,0 }; linklist *answer=insertion_sort(a,10); return 0;}

 

 

插入排序改进——二分查找插入排序

#include 
// 分类 -------------- 内部比较排序// 数据结构 ---------- 数组// 最差时间复杂度 ---- O(n^2)// 最优时间复杂度 ---- O(nlogn)// 平均时间复杂度 ---- O(n^2)// 所需辅助空间 ------ O(1)// 稳定性 ------------ 稳定void InsertionSortDichotomy(int A[], int n){ for (int i = 1; i < n; i++) { int get = A[i]; // 右手抓到一张扑克牌 int left = 0; // 拿在左手上的牌总是排序好的,所以可以用二分法 int right = i - 1; // 手牌左右边界进行初始化 while (left <= right) // 采用二分法定位新牌的位置 { int mid = (left + right) / 2; if (A[mid] > get) right = mid - 1; else left = mid + 1; } for (int j = i - 1; j >= left; j--) // 将欲插入新牌位置右边的牌整体向右移动一个单位 { A[j + 1] = A[j]; } A[left] = get; // 将抓到的牌插入手牌 }}int main(){ int A[] = { 5, 2, 9, 4, 7, 6, 1, 3, 8 };// 从小到大二分插入排序 int n = sizeof(A) / sizeof(int); InsertionSortDichotomy(A, n); printf("二分插入排序结果:"); for (int i = 0; i < n; i++) { printf("%d ", A[i]); } printf("\n"); return 0;}

 

 

插入排序再改进——希尔排序

希尔排序是基于插入排序的以下两点性质而提出改进方法的:

1、插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率;

2、但插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位;

 

希尔排序的基本思想是:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

#include 
using namespace std;void shell_sort(int a[], int n){ //设定每次的步长 for (int dk = n / 2; dk >= 1; dk = dk / 2) { //对每一组进行插入排序 for (int k = 0; k < dk; k++) { //子组,做简单的插入排序 for (int i = 1; k + i * dk < n; i++) { //确定插入位置 for (int j = 0; j <= i; j++) { if (a[k + j * dk] >= a[k + i * dk]) { int temp = a[k + i * dk]; for (int p = i - 1; p >= j; p--) { a[k + (p + 1)*dk] = a[k + p * dk]; } a[k + j * dk] = temp; break; } } } } }}int main() { int a[10] = { 3,6,4,9,7,1,2,5,8,0 }; shell_sort(a, 10); return 0;}

 

选择排序

无论什么数据进去都是 O(n²) 的时间复杂度。所以用到它的时候,数据规模越小越好。唯一的好处可能就是不占用额外的内存空间

算法思路:

第1趟,在待排序记录 r[1] ~ r[n] 中选出最小的记录,将它与 r[1] 交换;第2趟,在待排序记录 r[2] ~ r[n] 中选出最小的记录,将它与 r[2] 交换;以此类推,第i趟在待排序记录 r[i] ~ r[n] 中选出最小的记录,将它与 r[i] 交换,使有序序列不断增长直到全部排序完毕。

//选择排序	void select(int nums[]) {		if (nums == nullptr) return;		//获取数组长度		int len = sizeof(nums) / sizeof(int);		for (int i = 0; i < len-1; i++) {			int minIndex = i;			//循环找到最小的值,将其放在位置i处			for (int j = i + 1; j < len; j++) {				minIndex = nums[j] < nums[minIndex] ? j : minIndex;			}			nums[i] = nums[i] + nums[minIndex];			nums[minIndex] = nums[i] - nums[minIndex];			nums[i] = nums[i] - nums[minIndex];		}	}

 

 

快速排序

 

算法思路:

1、从数列中挑出一个元素,称为 “基准”(pivot);

2、重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作;

3、递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序;

递归实现

#include 
void quick_sort(int a[], int l, int r) { if (l >= r) return; int low = l, high=r; int key = a[low]; while (low < high) { //从右往左找,如果该值小于key,则左移到low所指的位置 for (;; high--) { if (high <= low) break; if (a[high] < key) { a[low] = a[high]; break; } } //从左往右找,如果该值大于key,则右移到high所指的位置 for (;; low++) { if (high <= low)break; if (a[low] > key) { a[high] = a[low]; break; } } } //把key放在low和high重合的位置(即key应该在数组中的最终位置) if (low == high) a[low] = key; //分而治之 quick_sort(a, l, low - 1); quick_sort(a, low+1, r);}int main(){ int a[10] = {2,6,3,8,9,0,1,4,7,5}; //递归 quick_sort(a, 0,9); return 0;}

 

非递归

 

 

6、堆排序

堆是一个完全二叉树(满二叉树的每个非叶子节点都有两个孩子节点,完全二叉树是前几层都是满的,左后一层叶子节点从左到右依次连续的,例如:

 

堆在实际过程中可以用数组来进行实现。位置 i 的左孩子的下标为  2* i + 1 右孩子下标为  2* i + 2 。堆的结构在脑海中,实际的存储形式是数组。位置为 i 的节点的父节点坐标为 (i-1)/2 

-1/2=0;

堆分为大根堆和小根堆,堆就是完全二叉树。

 

把数组变成大根堆的过程(找索引位置为 x 的节点的父节点,父节点的索引位置为 (x-1)/2 )

1、数组

2、考查范围(0到1时的堆)   由于此时 1 的父节点坐标 x=(1-1)/2 =0 ,计算出 1 的父节点为2,2>1 ,因此 1 不与2 进行交换。

3、考查范围(0-2时的堆)   

变换过程如下:

计算 3 的父节点为 (2-1)/2=0,因此3的父节点坐标为0,3 的父节点 2<3 ,此时需要将 3 和 2 的位置进行交换,数组变成

 

4、考查范围(0-3时的堆):

按照上述比较方式,6先和1进行交换,数组变成,然后6 再和3进行比较,发现自己比3大,因此两个数进行交换

 

代码实现思路:给你一个数组,依次把 0到 i 的位置的数加进来,将 i 位置元素的值与其父位置元素的值进行比较,只要我大,我们就换,直到不能继续往上换位置,最终形成大根堆。

 

heapinsert过程

#include
using namespace std;void swap(int arr[], int i, int father) { int temp = arr[i]; arr[i] = arr[father]; arr[father] = temp;}void helper(int arr[], int i) { //不断将其与其父节点进行比较,如果他更大,则交换两个节点的值 while (arr[i]>arr[(i-1)/2]) { swap(arr, i, (i - 1) / 2); i = (i - 1) / 2; }}void CreatHeap(int input [], int len) { if ((input == nullptr) || (len == 0)) return; //一次获取一个元素,考察他应该在堆中的位置 for (int i = 0; i < len; i++) { helper(input, i); }}int main() { int heap[] = { 2,1,3,6,0,4}; int len = sizeof(heap) / sizeof(int); CreatHeap(heap, len); return 0;}

 

 

堆中有值发生变化时

如果排好的大顶堆中,突然有一个的值变小了,则将该值与其两个孩子节点(位置 i 的左孩子的下标为  2* i + 1 右孩子下标为  2* i + 2 。)的值进行比较,将更大的孩子与其进行交换,然后再与新的两个孩子进行比较,依次比较至比两个孩子的值都大时,停止比较。

 

heapify过程

void heapify(int heap[], int index, int size) {	//确定左孩子的节点坐标	int left = index * 2 + 1;	//确保不越界	while (left <= size) {		//选择两个孩子里面的大值		int largest = (left + 1 < size && heap[left + 1] > heap[left]) 			? left + 1 			: left;		//将大值与原index的值进行比较,取两者中最大者		largest = heap[largest] > heap[index] ? largest : index;		//说明index已经是最大的值了		if (largest == index) break;		//将最大值放在index位		swap(heap, largest, index);		index = largest;		//重新确定左子树的位置,方便进行下一轮比较		left = index * 2 + 1;	}}

 

堆排序的全过程

先让数组变成大根堆,然后将堆顶元素与堆中最后一个元素进行交换,再将堆的尺寸减一,此时最大的值就固定在数组的最后一位了。将堆中剩余的元素进行heapify。这样,每次都能确定一个值的位置。

#include "pch.h"#include
using namespace std;void swap(int arr[], int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;}void helper(int arr[], int i) { //不断将其与其父节点进行比较,如果他更大,则交换两个节点的值 while (arr[i]>arr[(i-1)/2]) { swap(arr, i, (i - 1) / 2); i = (i - 1) / 2; }}void heapify(int heap[], int index, int size) { //确定左孩子的节点坐标 int left = index * 2 + 1; //确保不越界 while (left < size) { //选择两个孩子里面的大值 int largest = (left + 1 < size && heap[left + 1] > heap[left]) ? left + 1 : left; //将大值与原index的值进行比较,取两者中最大者 largest = heap[largest] > heap[index] ? largest : index; //说明index已经是最大的值了 if (largest == index) break; //将最大值放在index位 swap(heap, largest, index); index = largest; //重新确定左子树的位置,方便进行下一轮比较 left = index * 2 + 1; }}void CreatHeap(int input [], int len) { if ((input == nullptr) || (len == 0)) return; //一次获取一个元素,考察他应该在堆中的位置 for (int i = 0; i < len; i++) { helper(input, i); } int size = len; swap(input, 0, --size); while (size > 0) { heapify(input, 0, size); swap(input, 0, --size); }}int main() { int heap[] = { 2,1,3,6,0,4}; int len = sizeof(heap) / sizeof(int); CreatHeap(heap, len); return 0;}

 

 

 

归并排序

 

 

桶排序、基数排序和基数排序不是基于比较的排序

 

 

转载地址:http://ywfq.baihongyu.com/

你可能感兴趣的文章
2021年过氧化工艺试题及答案及过氧化工艺考试平台
查看>>
2021年重氮化工艺考试题库及重氮化工艺考试报名
查看>>
2021年车工(高级)考试总结及车工(高级)试题及答案
查看>>
2021年压力焊证考试及压力焊实操考试视频
查看>>
2021年低压电工考试及低压电工考试申请表
查看>>
2021年低压电工考试及低压电工考试申请表
查看>>
2021年A特种设备相关管理(电梯)考试APP及A特种设备相关管理(电梯)复审考试
查看>>
2021年N1叉车司机考试题及N1叉车司机复审模拟考试
查看>>
2021年危险化学品经营单位主要负责人考试APP及危险化学品经营单位主要负责人多少钱
查看>>
2021年T电梯修理考试技巧及T电梯修理模拟考试软件
查看>>
2021年电工(初级)考试及电工(初级)证考试
查看>>
大数据学习之Spark——00Spark项目的pom.xml文件
查看>>
从 MFC 移植程序到 wxWidgets 界面库 ——《定时执行专家 5.0》的界面实现
查看>>
CodeBlocks开发wxWidgets环境配置详细
查看>>
wxSqlite3 和 wxPropertyGrid 类库的说明
查看>>
天涯人脉通讯录 - 设计草图
查看>>
wxWidgets 最新版2.8.11,终于放出来了
查看>>
python学习09:暂停一秒后再输出
查看>>
6、ShardingSphere 之 读写分离
查看>>
C++ STL
查看>>