阅读原文请点击此处
本文代码托管于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就使用快速排序,否则就使用插入排序。