排序算法总结——c++实现

简介:  Java实现见链接:https://mp.weixin.qq.com/s/pN4RH4pPKtSkZJgcf2V-Vw  排序算法的稳定性分析 选择排序无法保证稳定性: 归并排序可...

 

Java实现见链接:https://mp.weixin.qq.com/s/pN4RH4pPKtSkZJgcf2V-Vw

 

 

排序算法的稳定性分析

 

选择排序无法保证稳定性

 

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

 

快速排序无法保持稳定性(因为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 <stdio.h>

// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 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 <iostream>
#include <vector>
using namespace std;

//传入一对迭代器,标准的插入排序
void Insertion_sort(vector<int>::iterator begin, vector<int>::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<int> 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<iostream>
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 <iostream>
#include <vector>
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 <stdio.h>

// 分类 -------------- 内部比较排序
// 数据结构 ---------- 数组
// 最差时间复杂度 ---- 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;
}

 

 

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

https://www.cnblogs.com/chengxiao/p/6104371.html

https://www.iqiyi.com/v_19rrhzyejc.html

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

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

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

 

希尔排序的基本思想是:

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

#include <iostream>
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];
		}
	}

 

 

快速排序

http://www.iqiyi.com/w_19rvedija1.html

 

算法思路:

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

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

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

递归实现

#include <iostream>

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;

}

 

非递归

 

 

堆排序

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

 

堆在实际过程中可以用数组来进行实现。位置 i 的左孩子的下标为  2* i + 1 右孩子下标为  2* i + 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 位置元素的值与其父位置元素的值进行比较,只要我大,我们就换,直到不能继续往上换位置,最终形成大根堆。

#include<iostream>
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[] = { 0,1,2,3,4,5,6,7 ,8};
	int len = sizeof(heap) / sizeof(int);
	CreatHeap(heap, len);

	return 0;
}

 

堆排序就是利用堆结构进行的一个排序

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

#include "pch.h"
#include<iostream>
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;
}

 

 

归并排序

 

 

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

 

 

相关文章
|
13天前
|
机器学习/深度学习 安全 算法
【图论】【割点】【C++算法】928. 尽量减少恶意软件的传播 II
【图论】【割点】【C++算法】928. 尽量减少恶意软件的传播 II
|
28天前
|
存储 算法 数据管理
【C/C++ 基础算法】 C/C++ 位图算法的使用
【C/C++ 基础算法】 C/C++ 位图算法的使用
35 0
|
1月前
|
自然语言处理 算法 C++
在C++语言中非修正算法
在C++语言中非修正算法
13 1
|
2月前
|
算法 JavaScript Java
【状态压缩】【动态规划】【C++算法】1125.最小的必要团队
【状态压缩】【动态规划】【C++算法】1125.最小的必要团队
|
2月前
|
机器学习/深度学习 算法 测试技术
【动态规划】【C++算法】2742. 给墙壁刷油漆
【动态规划】【C++算法】2742. 给墙壁刷油漆
|
2月前
|
算法 测试技术 C++
【动态规划】【状态压缩】【C++算法】1815 得到新鲜甜甜圈的最多组数
【动态规划】【状态压缩】【C++算法】1815 得到新鲜甜甜圈的最多组数
|
1月前
|
机器学习/深度学习 算法 程序员
C++ Algorithm 库 算法秘境探索(Algorithm Wonderland Exploration)
C++ Algorithm 库 算法秘境探索(Algorithm Wonderland Exploration)
74 1
|
28天前
|
缓存 算法 C语言
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
【C++ 标准查找算法 】C++标准库查找算法深入解析(In-depth Analysis of C++ Standard Library Search Algorithms)
46 0
|
13天前
|
算法 测试技术 C#
【广度优先搜索】【堆】【C++算法】407. 接雨水 II
【广度优先搜索】【堆】【C++算法】407. 接雨水 II
|
13天前
|
算法 测试技术 Serverless
【二分查找】【C++算法】378. 有序矩阵中第 K 小的元素
【二分查找】【C++算法】378. 有序矩阵中第 K 小的元素

热门文章

最新文章