几大典型排序算法的c++代码实现及总结

        用C++类模板写了个排序算法的类,里面包含了几大经典算法的实现,例如插入排序、希尔排序、堆排序、冒泡排序、快速排序等。经测试该类可以很好地实现功能,方便直接引用。

// 几大典型的排序算法介绍以及代码实现


#ifndef _SORT_H_
#define _SORT_H_

#include <vector>
using namespace std;

template <typename T>
class CSort{
public:
	typedef vector<T> type;
	
	enum sortType{
		INSERTION_SORT,    // 直接插入排序
		SHELL_SORT,        // 希尔排序
		SELECTION_SORT,    // 选择排序
		HEAP_SORT,         // 堆排序
		BUBBLE_SORT,       // 冒泡排序
		QUICK_SORT,        // 快速排序
		MERGE_SORT,        // 归并排序
		BUCKET_SORT        // 桶排序
	};

	sortType m_sortType;

	CSort(type vec) : m_vec(vec)  {}
	CSort()  {}
	~CSort()  {}

	// 设置排序类型
	void SetSortType(sortType st)  { m_sortType = st; }

	// 设置数组内容
	void SetVector(type a) { m_vec = a; }

	// 返回数组对象
	type GetVector() const { return m_vec; }


	// 1. 直接插入排序
	void InsetionSort ();

	// 2. 希尔排序
	void ShellSort ();

	// 3. 选择排序
	void SelectionSort ();

	// 4. 堆排序
	void HeapSort();

	// 5. 冒泡排序
	void BubbleSort();

	// 6. 快速排序
	void QuickSort();
	void QuickSort(int left, int right);

	// 7. 归并排序
	void MergeSort();
	void MergeSort(type &tmpArray, int left, int right);
	void merge(type &tmpArray, int leftPos, int rightPos, int rightEnd);

	// 8. 桶排序
	void BucketSort();

private:
	type m_vec;
};

///
//

// 打印数组内容
template <typename T>
static void PrintVector(const vector<T> &a)
{
	for(int i=0; i<a.size(); i++) {
		if( i != a.size()-1)
			cout<<a[i]<<" ";
		else
			cout<<a[i]<<endl;
	}
}

/**
 * 1. 直接插入排序(InsertionSort):
 *             将一个记录插入到已排序好的有序表中,从而得到一个新记录数增1的有序表。
 *         即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,
 *         直至整个序列有序为止。
 */
template <typename T>
void CSort<T>::InsetionSort () 
{
	int j;
	for (int i=1; i<m_vec.size(); i++) {
		T temp = m_vec[i];
		for(j=i; j>0 && temp<m_vec[j-1]; j--)
			m_vec[j] = m_vec[j-1];
		m_vec[j] = temp;
	}
}


/**
 * 2. 希尔排序或缩小增量排序(ShellSort):
 *        操作方法:
 *           1.选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
 *           2.按增量序列个数k,对序列进行k 趟排序;
 *           3.每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。
 *             仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。
 */
template <typename T>
void CSort<T>::ShellSort () 
{
	for(int gap = m_vec.size()/2; gap > 0; gap /= 2) {   // 采用希尔增量
		for( int i=gap; i<m_vec.size(); i++ ) {
			T temp = m_vec[i];
			int j;
			for(j = i; j >= gap && temp <m_vec[j-gap]; j -= gap)
				m_vec[j] = m_vec[j-gap];
			m_vec[j] = temp;
		}
	}
}

/**
 * 3. 选择排序(SelectionSort):
              在要排序的一组数中,选出最小(或者最大)的一个数与第1个位置的数交换;
 *        然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,
 *        直到第n-1个元素(倒数第二个数)和第n个元素(最后一个数)比较为止。
 */
template <typename T>
static int GetMinValueIndex(const vector<T> &vec, int pos)
{
	int k = pos;
	for(int i=pos+1; i<vec.size(); i++) {
		if(vec[i] < vec[k]) k = i;
	}
	return k;
}

template <typename T>
void CSort<T>::SelectionSort() 
{
	for(int i=0; i<m_vec.size()-1; i++) {
		int k = GetMinValueIndex(m_vec, i);
		T tmp = m_vec[i];
		m_vec[i] = m_vec[k];
		m_vec[k] = tmp;
		PrintVector(m_vec);
	}
}


/**
 *  4. 堆排序(HeapSort):
 *            初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,
 *        使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者
 *        最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元
 *        素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为
 *        堆排序。
 *        实现堆排序需要解决2个问题:
 *        1.如何将n个待排序的数建成堆;
 *        2.输出堆顶元素,如何调整剩下的n-1个数,成为一个新堆。
 */

/** 
 *  将n个待排序的数建成小顶堆:
 *     建堆方法:
 *     1. n个节点的完全二叉树,最后一个节点是第[n/2]-1个节点的孩子(标号从0开始);
 *     2. 筛选从第[n/2]-1个节点开始,使该子树成为堆;
 *     3. 之后依次向前,使节点为根的子树成为堆,直到根节点。
 */
template <typename T>
static void BuildHeap(vector<T> &a)
{
	for(int i = a.size()/2-1; i>=0; i--) 
		HeapAdjust(a, i, a.size());
	PrintVector(a);   // 输出建堆后数组中内容
}

template <typename T>
static void HeapAdjust(vector<T> &a, int pos, int length)
{
	int child = 2*pos+1;  // 左节点在数组中的位置
	while(child < length) {
		if(child +1 < length && a[child +1]<a[child])   // 如果右节点存在并且右节点值小于左节点,将当前位置指向右节点
			++child;
		if( a[pos]>a[child] ) {
			T temp = a[pos];
			a[pos] = a[child];
			a[child] = temp;
			pos = child;       // 交换后对孩子节点做同样操作,使之成为堆
			child = pos*2+1;   
		}
		else
			break;   // 不需要调整直接退出
	}
}

template <typename T>
void CSort<T>::HeapSort()
{
	BuildHeap(m_vec);
	
	type tmpArray(m_vec.size());
	// 从最后一个元素开始对序列调整
	for(int i=m_vec.size()-1, j=0; i>=0; i--, j++) {
		tmpArray[j] = m_vec[0];
		m_vec[0] = m_vec[i];
		m_vec[i] = tmpArray[j];
		HeapAdjust(m_vec, 0, i);
	}
	m_vec = tmpArray;
	PrintVector(m_vec);
}

/**
 *  5. 冒泡排序(BubbleSort):
 *          在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次
 *      进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排
 *      序与排序要求相反时,就将它们互换。
 */
template <typename T>
void CSort<T>::BubbleSort()
{
	for(int i=0; i<m_vec.size(); i++) {
		bool flag = false;
		for(int j=m_vec.size()-1; j>i; j--) {
			if( m_vec[j] < m_vec[j-1]) {
				T tmp = m_vec[j];
				m_vec[j] = m_vec[j-1] ;
				m_vec[j-1] = tmp; 
				flag = true;
			}
		}
		if(!flag) break;     // 某次循环没有交换冒泡,则序列已经有序,直接退出
		else PrintVector(m_vec);
	}
}


/**
 *  6. 快速排序(QuickSort):
 *         1)选择一个基准元素pivot,方便起见通常选择第一个或最后一个元素;
 *         2)通过一趟排序将待排序的记录分割成独立的两部分,其中一部分记录的元素值都比基准元素小,
 *            另一部分记录的元素都比基准元素大,此时基准元素在其排好序后的正确位置。
 *         3)然后分别对这两部分记录用同样的方法排序,直到整个记录有序。
 */
template <typename T>
static inline void swap(T *a, T *b)
{
	T tmp = *a;
	*a = *b;
	*b = tmp;
}

template <typename T>
void CSort<T>::QuickSort()
{
	QuickSort(0, m_vec.size()-1);
}

template <typename T>
void CSort<T>::QuickSort(int left, int right)
{
	if(left < right)  {
		T pivot = m_vec[left];   // 选择最左边的数作为基准,然后将其放到最后的位置
		swap(&m_vec[left], &m_vec[right]);

		int i = left-1, j = right;
		for( ; ; ) {
			while( m_vec[++i] < pivot ) {}
			while( m_vec[--j] > pivot ) {}
			if(i < j) swap(&m_vec[i], &m_vec[j]);
			else break;
		}
		swap(&m_vec[i], &m_vec[right]);
		PrintVector(m_vec);

		QuickSort(left, i-1);
		QuickSort(i+1, right);
	}
}


/**
 *  7. 归并排序(MergeSort):
 *             归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为
 *         若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。
 */
template <typename T>
void CSort<T>::MergeSort()
{
	vector<T> tmpArray( m_vec.size() );

	MergeSort(tmpArray, 0, m_vec.size()-1 );
}

template <typename T>
void CSort<T>::MergeSort(type &tmpArray, int left, int right) 
{
	if(left < right) {  // 分治策略实现
		int center = (left + right) /2;
		MergeSort(tmpArray, left, center);
		MergeSort(tmpArray, center+1, right);

		merge(tmpArray, left, center+1, right);
	}
}

template <typename T>
void CSort<T>::merge(type &tmpArray, int leftPos, int rightPos, int rightEnd)
{
	int leftEnd = rightPos - 1;
	int tmpPos = leftPos;
	int numElements = rightEnd - leftPos + 1;

	while (leftPos <= leftEnd && rightPos <= rightEnd) {
		if(m_vec[leftPos] <=  m_vec[rightPos])
			tmpArray[tmpPos++] = m_vec[leftPos++];
		else
			tmpArray[tmpPos++] = m_vec[rightPos++];
	}

	while (leftPos <= leftEnd)   // copy rest of left array
		tmpArray[tmpPos++] = m_vec[leftPos++];

	while (rightPos <= rightEnd)   // copy rest of right array
		tmpArray[tmpPos++] = m_vec[rightPos++];

	for(int i=0; i<numElements; i++, rightEnd--)
		m_vec[rightEnd] = tmpArray[rightEnd];

	PrintVector(m_vec);
}


/**
 *  8. 桶排序(BucketSort):
 *           是将序列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式
 *       继续使用桶排序进行排序)。 
 *           例如要对大小为[1..1000]范围内的n个整数A[1..n]排序。首先,可以把桶设为大小为10的范围,具体
 *       而言,设集合B[1]存储[1..10]的整数,集合B[2]存储(10..20]的整数,……集合B[i]存储((i-1)*10,i*10]
 *       的整数,i=1,2,..100。总共有100个桶。然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]
 *       中。再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择乃至快排,一般来说任何排序法都可以。
 *       最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,整个序列就有序了。  
 */

template <typename T>
void CSort<T>::BucketSort()
{
	cout<<"BucketSort is not completed!"<<endl;
}



#endif

各种排序的时间复杂度,空间复杂度和稳定性如下表所示。

排序方法

时间复杂度

空间复杂度

稳定性

平均情况

最好情况

最坏情况

辅助存储

直接插入

O(N2)

O(N)

O(N2)

O(1)

稳定

希尔排序

O(N7/6)?

O(N)

O(N2)

O(1)

不稳定

直接选择

O(N2)

O(N2)

O(N2)

O(1)

不稳定

堆排序

O(NlogN)

O(NlogN)

O(NlogN)

O(1)

不稳定

冒泡排序

O(N2)

O(N)

O(N2)

O(1)

稳定

快速排序

O(NlogN)

O(NlogN)

O(N2)

O(NlogN)

不稳定

归并排序

O(NlogN)

O(NlogN)

O(NlogN)

O(N)

稳定



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值