常见的排序算法

22 篇文章 0 订阅
3 篇文章 0 订阅

阅读原文请点击此处

本文代码托管于Github,阅读源码请点击此处

交换排序

冒泡排序

冒泡排序是我接触最早的排序方法,第一次接触冒泡排序是在大一上C语言课的时候,

冒泡排序是最简单的一种排序算法。

//代码解析冒泡排序--> 小-->大
void BubbleSort(std::vector<int>& v)
{
    for(size_t i = 0; i < v.size() - 1; ++i)
    {
        for(size_t j = 0; i < v.size() - i - 1; ++i)
        {
            if(v[i] > v[j])
            {
                swap(v[i], v[j]);
            }
        }
    }
}

快速排序

左右指针法

#pragma once
#include "Common.h"
/********************快速排序---begin************************/

template<class T, class Com = Greater<T>>
class QuickSortLRPtr
{
public:
	void Sort(std::vector<T>& v)
	{
		LRPoint(v, 0, v.size() - 1);		//左右指针法
	}

private:
	/***************左右指针法---begin******************/
	void LRPoint(std::vector<T>& v, int left, int right)
	{
		if (left >= right)
			return;
		int div = Partition(v, left, right);
		LRPoint(v, left, div - 1);
		LRPoint(v, div + 1, right);
	}

	int Partition(std::vector<T>& v, int left, int right)
	{
		int prvot = left;
		Com comper;

		while (left < right)
		{
			while (comper(v[right] , v[prvot]) && left < right)
				right--;
			while (comper(v[prvot] , v[left]) && left < right)
				left++;
			
			std::swap(v[right], v[left]);
		}
		std::swap(v[right], v[prvot]);
		return right;
	}
	/***************左右指针法---end******************/

	
};

挖坑法

template<class T, class Com = Greater<T>>
class QuickSortDigHole
{
public:
	void Sort(std::vector<T>& v)
	{
		DigHole(v, 0, v.size() - 1);		//挖坑法
	}
private:
	/****************挖坑法---begin********************/
	void DigHole(std::vector<T>& v, int left, int right)
	{
		if (left >= right)
			return;
		int div = DigHoleHandler(v, left, right);
		DigHole(v, left, div - 1);
		DigHole(v, div + 1, right);
	}

	int DigHoleHandler(std::vector<T>& v, int left, int right)
	{
		T obj = v[left];
		Com comper;
		while (left < right)
		{
			while (comper(obj, v[right]) && left < right)
				right--;
			v[left] = v[right];
			while (comper(v[left], obj) && left < right)
				left++;
			v[right] = v[left];
		}
		v[right] = obj;
		return right;
	}
	/****************挖坑法---end********************/
};

选择排序

直接选择排序

遍历N-1次数据,每次遍历找出数据中最大/小的元素,并与起始/末尾的元素交换,经过N-1次遍历后,数据则排序完成。

通过一次遍历,将序列中最大/小的元素下标记录下来,当比较至序列尾部时,将序列中最大的元素与尾部的元素交换,序列抛弃尾部元素,循环此过程。

void SelectSort(std::vector<int> & v)
{
	int max_index = 0;
	for (size_t index = v.size() - 1; index > 0; --index)
	{
		max_index = 0;
		for (size_t search_index = 0; search_index <= index; ++search_index)
		{
			if (v[max_index] < v[search_index])
			{
				max_index = search_index;
			}
		}
		swap(v[max_index], v[index]);
	}
}

直接选择排序优化版

排序思路基本不变,设置序列中最大&最小的元素下标记录器,遍历一次序列,将序列中最大的元素与最末尾的元素交换,序列中最小的元素与序列中第一个元素交换,抛弃序列头部和尾部的元素,重复此过程。

void SelectOptSort(std::vector<int> & v)
{
	size_t tmp_index;
	size_t max_index;
	size_t min_index;
	size_t start_index = 0;
	size_t end_index = v.size() - 1;

	while (start_index < end_index)
	{
		tmp_index = start_index;
		max_index = tmp_index;
		min_index = tmp_index;

		while (tmp_index <= end_index)
		{
			if (v[max_index] < v[tmp_index])
				max_index = tmp_index;
			if (v[min_index] > v[tmp_index])
				min_index = tmp_index;
			tmp_index++;
		}

		swap(v[start_index], v[min_index]);
		if (start_index == max_index)
			max_index = min_index;

		swap(v[end_index], v[max_index]);

		start_index++;
		end_index--;
	}
}

NOTE

优化的选择排序应注意第一次交换之后导致min_index/max_index变化的问题。

堆排序

采用堆的特性,每次拿出堆顶的元素,再重新调整堆。

向下调整法

即就是从堆的最后一个父亲节点开始调整,让每一个父亲节点下的左右孩子都小于该父亲节点。

  • 从堆的最后一个父亲节点开始调整
  • 选择孩子节点中较大的一个与父亲节点比较
  • 如果父亲节点比较大的孩子节点小的话,就交换父亲节点
    • 交换之后,父亲节点等于交换之后的叶子节点
    • 新的孩子节点继续向下寻找,直到没有孩子节点为止
  • 完成一轮调整之后,让父亲节点向后调整,并重复上述过程
void _AdjustDown(std::vector<int>& v, size_t parent, size_t len)
{
	size_t l_chr = parent * 2 + 1;  //计算出左孩子的长度
	while (l_chr <= len)
	{
		size_t r_chr = l_chr + 1; //右孩子下标
		size_t biger_chr = l_chr;
		if (r_chr <= len)
		{
			biger_chr = v[l_chr] > v[r_chr] ? l_chr : r_chr;
		}
		if (v[parent] < v[biger_chr])
		{
			std::swap(v[parent], v[biger_chr]);
		}
        parent = biger_chr;
		l_chr = l_chr * 2 + 1;
	}
}
void AdjustDown(std::vector<int>& v, int len)
{
	int parent = (len - 1) >> 1;
	for (parent; parent >= 0; --parent)
	{
		_AdjustDown(v, parent, len);
	}
}
void HeapSort(std::vector<int>& v)
{
	//采用向下调整法
	for (int i = v.size() - 1; i >= 0; --i)
	{
		AdjustDown(v, i);  //每进行一次,将v中前n个最大的元素换到堆顶
		std::swap(v[0], v[i]);
	}
}

向上调整法

向上调整法即就是从堆的最后一个节点开始,从孩子节点向上调整,直到调整到的最上端的根节点,每一个节点都重复此过程,当到达根节点时,堆即调整完成。

void _AdjustUp(std::vector<int>& v, int child)
{
	while (child > 0)
	{
		int parent = (child - 1) >> 1;
        while(child > 0)
        {
			if (v[parent] < v[child])
			{
				std::swap(v[parent], v[child]);
			}
			child = parent;
            parent = (child - 1) >> 1;
        }
	}
}

void AdjustUp(std::vector<int>& v, int child)
{
	//这里的child组的最后一个元素下标
	for (child; child > 0; --child)
	{
		_AdjustUp(v, child);
	}
}
对堆排序进行优化

使用上面的方式进行堆排序时,维护堆是一种徒劳的方式,实际上,每一次调整只需要选出堆中的最大的元素,不需要将左右子树都维护成一个合格的堆,所以我们可以降低维护堆所带来的成本问题

#pragma once
#include <vector>
#include <iostream>

struct Greater
{
	bool operator()(const int& child, const int& parent)
	{
		return child > parent;
	}
};

struct Lesser
{
	bool operator()(const int& child, const int& parent)
	{
		return child < parent;
	}
};


template<class T, class Com = Greater>
class HeapSort
{
public:
	void Sort(std::vector<T>& v)
	{
		for (int len = v.size(); len > 0; --len)
		{
			//AdjustDown(v, len);	//向下调整
			AdjustUp(v, len);	//向上调整
			std::swap(v[0], v[len- 1]);
		}
	}
private:
	/*************堆排序向下调整法---begin************/

	void AdjustDown(std::vector<T>& v, const int& len)
	{
		int parent = len / 2 - 1;
		for (parent; parent >= 0; --parent)
		{
			_AdjustDown(v, parent, len);
		}
	}

	void _AdjustDown(std::vector<T>& v, int parent, const int& len)
	{
		Com comper;
		int lchr = parent * 2 + 1;
		int biger_chr = lchr;
		int rchr = lchr + 1;
		if (rchr < len)
		{
			biger_chr = v[rchr] > v[lchr] ? rchr : lchr;
		}

		if (comper(v[biger_chr], v[parent]))
		{
			std::swap(v[biger_chr], v[parent]);
		}
		parent = biger_chr;
		lchr = parent * 2 + 1;
	}
	/*************堆排序向下调整法---end*******************/

	/*************堆排序向上调整法---begin*****************/

	void AdjustUp(std::vector<T>& v, const int& len)
	{
		int child = len - 1;
		for (child; child > 0; --child)
		{
			_AdjustUp(v, child, len);
		}
	}


	void _AdjustUp(std::vector<T>& v, int child, const int& len)
	{
		Com comper;
		int parent = (child - 1) >> 1;
		if (comper(v[child], v[parent]))
		{
			std::swap(v[parent], v[child]);
		}
	}
	/*************堆排序向上调整法---end*******************/
};

插入排序

直接插入排序

将一个序列的元素插入另外一个已经经过排序序列,根据元素的大小决定插入的位置,直到元素全部插入为止。

template<class T, class Com = Greater<T> >
class InsertSort
{
public:
	void Sort(std::vector<T>& v)
	{
		for (int i = 1; i < static_cast<int>(v.size()); ++i)
		{
			T obj = v[i];	//先将要插入的数据保存起来
			int pos = 0;
			Com comper;
            /*	今天发现这样寻找位置的方式是十分挫的!
			while (pos < i && comper(v[i], v[pos]))
				pos++;
			if(pos == i)
				continue;	//找到的合适位置即就是当前位置

			for (int j = i; i > pos; --i)
			{
				std::swap(v[i], v[i - 1]);
			}

			v[pos] = obj;
			*/
            
            //从已经有序的最后一个元素开始寻找
            for(pos = i - 1; pos >= 0; --pos)
            {
                if(comper(v[pos], obj) || v[pos] == obj)
                    break;
                else
                    v[pos + 1] = v[pos];
            }
            v[pos + 1] = obj;
            
		}
	}
};

折半插入排序

折半插入排序的思想与直接插入排序是一样的,不同之处在于在寻找插入位置时,折半插入排序使用二分法寻找目标位置。

/********************折半插入排序---begin*********************/

/*查找思想与直接插入查找思想类似,不过在寻找要插入位置时,采用二分查找*/
template<class T, class Com = Greater<T>>
class HalfInsertSort
{
public:
	void Sort(std::vector<T>& v)
	{
		for (int i = 0; i < v.size(); ++i)
		{
			T obj = v[i];
			int pos = 0;
			pos = GetInsertPos(v, i);
			if (pos == i)
				continue;

			for (int j = i; i > pos; --j)
			{
				std::swap(v[i], v[i - 1]);
			}

			v[pos] = obj;
		}
	}
private:
	//折半查找寻找位置
	size_t GetInsertPos(const std::vector<T>& v, const T& obj, int pos)
	{
		int left = 0;
		int right = pos;
		Com comper;	//仿函数
		int middle = 0;
		while (left < right)
		{
			middle = (left + right) >> 1;
			if (comper(v[middle], obj))
			{
				if (v[middle] > obj)
					right = middle - 1;
				else
					left = middle + 1;

			}
			else
			{
				if (v[middle] > obj)
					left = middle + 1;
				else
					right = middle - 1;
			}
		}

		return middle;
	}
};

/********************折半插入排序---end***********************/

希尔排序

刚开始学习希尔排序时一直存在一个误区,我总是会认为希尔排序是将间隔gap的所有元素组成一个新的集合,对这个新的集合进行直接插入排序,这样是不对的。

  • 希尔排序首先会确定出一个间隔的数字(gap),这个间隔的数字确定的方法我在网上遇到过两种,一种是直接使用集合大小除2,另一种是用集合大小除3加1,这两种方法的区别应该是在于性能方面的。
  • 当确定好gap之后,从pos位置开始,这个pos位置一直从gap增长到size-1,分别让pos和pos-gap组成一个集合,对这个集合进行直接插入排序
  • 以我的理解方式,新组织的集合就总是会有两个元素,如果这样理解是错误的,希望可以帮我指出。
/******************希尔排序---begin*********************/
template<class T, class Com = Greater<T> >
class ShellSort
{
public:
	void Sort(std::vector<T>& v)
	{
		int gap = v.size();
		while (1)
		{
			gap = gap / 3 + 1;
			//gap = gap / 2;
			DirInsertSort(v, v.size(), gap);
			//对每一组进行直接插入排序
			if (gap == 1)
				break;
		}
	}
private:
	void DirInsertSort(std::vector<T>& v, const int& size, const int& gap)
	{
		//对每个gap进行直接插入排序
		Com comper;
		for (int i = gap; i < size; ++i)	//从gap的位置开始,一直到最后一个元为止
		{
			T obj = v[i];
			int j = 0;
			for (j = i - gap; j >= 0; j -= gap)
			{
				if (comper(obj, v[j]) || v[j] == obj)
					break;
				else
					v[j + gap] = v[j];
			}
			v[j + gap] = obj;
		}
	}
};
/******************希尔排序---end***********************/

归并排序

归并排序主要可以分为合并两个有序数组+分治思想

  • 合并两个有序数组
  • 分治思想:将当前数组从中间切开,递归使左边以及右边的新数组都重复此动作,直到切开之后的数组是一个有序数组时,进行两个数组的合并
template<class T, class Com = Greater<T>>
class MergeSort
{
public:
	//合并两个有序数组
	void Merge(std::vector<T>& v, int L, int M, int R)
	{
		Com comper;
		int left_size = M - L + 1;	//	确定左边的数组的大小
		int right_size = R - M;	//确定右边的数组的大小
		int left_index = 0, right_index = 0;
		std::vector<T> v_lhs;
		std::vector<T> v_rhs;

		//将原数组拆分为两个已经排好序的数组
		for(left_index = L; left_index < M + 1; ++left_index)
		{
			//搬移左边的元素
			v_lhs.push_back(v[left_index]);
		}

		for(right_index = M  + 1; right_index < R + 1; ++right_index)
		{
			//搬移右边的数组
			v_rhs.push_back(v[right_index]);
		}

		left_index = 0;
		right_index = 0;
		int k = L;

		while(left_index < left_size && right_index < right_size)
		{
			//将分开的两个有序数组进行合并并写入原数组
			if(comper(v_rhs[right_index], v_lhs[left_index]))
			{
				v[k] = v_lhs[left_index];
				left_index++;
				k++;
			}
			else
			{
				v[k] = v_rhs[right_index];
				right_index++;
				k++;
			}
		}

		//搬移剩余元素
		while(left_index < left_size)
		{
			v[k] = v_lhs[left_index];
			left_index++;
			k++;
		}

		while(right_index < right_size)
		{
			v[k] = v_rhs[right_index];
			right_index++;
			k++;
		}
	}

	//进行分治
	void _MergeSort(std::vector<T>& v, int L, int R)
	{
		if (L == R)
			return;
		int M = (L + R) / 2;
		_MergeSort(v, L, M);
		_MergeSort(v, M + 1, R);
		Merge(v, L, M, R);
	}
};


STL中的sort算法

查阅资料得知,STL中的sort算法主要集成了插入排序和快速排序,在sort算法中存在一个界限值SORT_MAX来进行判断,如果需要排序的元素数量大于SORT_MAX就使用快速排序,否则就使用插入排序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值