数据结构(十二)——排序算法

一、排序简介

1、排序的一般定义

排序是计算机中经常进行的操作,目的在于将一组无序的数据元素调整为有序的数据元素。 序列:1,20,45,5,2,12 排序后:1,2,5,12,20,45

2、排序的数学定义

3、排序的稳定性

如果序列中的两个元素R[i]、R[j],关键字分别为K[i]、K[j],并且在排序之前R[i]排在R[j]前面,如果排序操作后,元素R[i]仍然排在R[j]前面,则排序方法是稳定的;否则排序是不稳定的。

4、排序实现的关键

比较:任意两个数据元素通过比较操作确定先后次序。 交换:数据元素需要交换才能得到预期的结果。

5、排序算法的性能评价

A、时间性能 主要性能差异体现在比较和交换的数量 B、辅助存储空间 为完成排序操作需要的额外的存储空间 必要时可以空间换时间 C、算法的实现复杂性 过于复杂的排序算法影响可读性和可维护性

6、排序类

class Sort:public Object
 {
 private:
   Sort();
   Sort(const Sort& other);
   Sort& operator = (const Sort& other);
   template <typename T>
   static void Swap(T& a, T& b)
   {
     T temp;
     temp = a;
     a = b;
     b = temp;
   }
 }

二、选择排序

1、选择排序简介

每次(第i次,i=1,2,3,...,n-2)从后面n-i个待排序的数据元素中选出关键字最小的元素,作为有序序列的第i个元素。 第i次选择排序示例: 选择排序实例:

2、选择排序的实现

选择排序的实现:

/******************************************
  * 排序方式:选择排序
  * array:序列
  * len:序列中元素个数
  * min2max:按从小到大进行排序
  * ***************************************/
 template <typename T>
 static void Select(T array[], int len, bool min2max = true)
 {
   for(int i = 0; i < len; i++)
   {
       int min = i;//从第i个元素开始
       //对待排序的元素进行比较
       for(int j = i + 1; j < len; j++)
       {
           //按排序的方式选择比较方式
           if(min2max?(array[min] > array[j]):(array[min] < array[j]))
           {
               min = j;
           }
       }
       if(min != i)
       {
          //元素交换
          Swap(array[i], array[min]);
       }
   }
 }

选择排序的时间复杂度为O(n^2)。 选择排序是不稳定的排序方法。

三、插入排序

1、插入排序简介

当插入第i(i>=1)个元素时,前i-1个元素已经排序好,用第i个元素的关键字与前i-1个元素的关键字分别进行比较,找到位置后将第i个元素插入,原来位置上的元素向后顺移。 第i次插入排序示例: 插入排序实例:

2、插入排序的实现

/******************************************
 * 排序方式:插入排序
 * array:序列
 * len:序列中元素个数
 * min2max:按从小到大进行排序
 * ***************************************/
template <typename T>
static void Insert(T array[], int len, bool min2max = true)
{
	for(int i = 1; i < len; i++)
	{
			int k = i;
			T temp = array[i];
			for(int j = i -1; (j > 0) && (min2max?(array[j] > temp):(array[j] < temp)); j--)
			{
					array[j + 1] = array[j];
					k = j;
			}
			if(k != i)
			{
					array[k] = temp;
			}
	}
}

插入排序的时间复杂度为O(n^2) 插入排序是稳定的排序方法。

四、冒泡排序

1、冒泡排序简介

每次从后向前进行(第i次),j=n-1,n-2,...,i,比较V[j-1]和V[j]的关键字,如果发生逆序,则交换V[j-1]和V[j]。 第i次冒泡排序示例: 冒泡排序实例:

2、冒泡排序的实现

/**********************************************
 * 排序方式:冒泡排序
 * array:序列
 * len:序列中元素个数
 * min2max:按从小到大进行排序
 * *******************************************/
template <typename T>
static void Bubble(T array[], int len, bool min2max = true)
{
		bool exchange = true;
		//遍历所有元素
		for(int i = 0; (i < len) && exchange; i++)
		{
				exchange = false;
				//将尾部元素与前面的每个元素作比较交换
				for(int j = len - 1; j > i; j--)
				{
						if(min2max?(array[j] < array[j-1]):(array[j] > array[j-1]))
						{
								//交换元素位置
								Swap(array[j], array[j-1]);
								exchange = true;
						}
				}
		}
}

冒泡排序的时间复杂度为O(n^2) 冒泡排序是稳定的排序方法。

五、希尔排序

1、希尔排序简介

将待排序序列划分为若干组,在每一组内进行插入排序,以使整个序列基本有序,然后再对整个序列进行插入排序。 将n个数据元素分成d个子序列,划分方法如下: d为增量,d的值在排序过程中由大到小逐渐缩小,直至最后一趟排序减为1。

2、希尔排序的实现

/******************************************
 * 排序方式:希尔排序
 * array:序列
 * len:序列中元素个数
 * min2max:按从小到大进行排序
 * ***************************************/
template <typename T>
static void Shell(T array[], int len, bool min2max = true)
{
		int d = len;
		do
		{
			 d = d/3 + 1;
			 for(int i = d; i < len; i += d)
			 {
					 int k = i;
					 T temp = array[i];
					 for(int j = i -d; (j >= 0) && (min2max?(array[j] > temp):(array[j] < temp)); j -= d)
					 {
							 array[j+d] = array[j];
							 k = j;
					 }
					 if(k != i)
					 {
								array[k] = temp;
					 }
			 }
		}while(d > 1);
}
};

希尔排序通过分组的方法进行多次插入排序,是一种不稳定的排序方法,时间复杂度为O(n^(3/2))。

六、归并排序

1、归并排序简介

将两个或两个以上的有序序列合并成一个新的有序序列。 将2个有序序列归并为一个新的有序序列,称为2路归并。 将N个有序序列归并为一个新的有序序列,称为N路归并。 2路归并实例:

2、归并排序实现

template <typename T>
static void Merge(T src[], T helper[], int begin, int mid, int end, bool min2max=true)
{
		int i = begin;
		int j = mid + 1;
		int k = begin;
		while((i <= mid) && (j <= end))
		{
				if(min2max ? (src[i] < src[j]) : (src[i] > src[j]))
				{
						helper[k++] = src[i++];
				}
				else
				{
						helper[k++] = src[j++];
				}
		}
		while(i <= mid)
		{
				 helper[k++] = src[i++];
		}
		while(j <= end)
		{
				 helper[k++] = src[j++];
		}
		//拷贝辅助空间的结果到源序列空间
		for(i = begin; i <= end; i++)
		{
				src[i] = helper[i];
		}
}
template <typename T>
static void Merge(T src[], T helper[], int begin, int end, bool min2max=true)
{
		if(begin < end)
		{
				int mid = (begin + end) / 2;
				//左边路进行归并排序
				Merge(src, helper, begin, mid, min2max);
				//右边路进行归并排序
				Merge(src, helper, mid+1, end, min2max);
				//二路归并排序
				Merge(src, helper, begin, mid, end, min2max);
		}
}
/******************************************
* 排序方式:归并排序
* array: 序列
* len:序列中元素个数
* min2max:按从小到大进行排序
* ***************************************/
template <typename T>
static void Merge(T* array, int len, bool min2max=true)
{
	//辅助空间申请
	T* helper = new T[len];
	if(helper != NULL)
	{
		Merge(array, helper, 0, len-1, min2max);
	}
	delete [] helper;
}

归并排序是一种稳定排序,需要额外的辅助空间完成,空间复杂度为O(n),时间复杂度为O(nlogn)。

七、快速排序

1、快速排序简介

任取序列中的某个数据元素作为基准将整个序列划分为左右两个子序列。左侧子序列中所有的数据元素都小于或等于基准元素,右侧子序列中所有元素都大于基准元素,基准元素排在两个子序列中间。 分别对两个子序列进行重新划分,直到所有的数据元素都排在相应位置上为止。 快速排序示例: 快速排序实例:

2、快速排序实现

/******************************************
 * 快速排序的区域划分函数
 * array:序列
 * begin:序列的起始位置
 * end:序列的结束位置
 * min2max:按从小到大进行排序
 * ***************************************/
template <typename T>
static int Partition(T array[], int begin, int end, bool min2max)
{
		T pv = array[begin];
		while(begin < end)
		{
				while((begin < end) && (min2max ? (array[end] > pv): (array[end] < pv)))
				{
						end--;
				}
				Swap(array[begin], array[end]);
				while((begin < end) && (min2max ? (array[end] <= pv): (array[end] > pv)))
				{
						begin++;
				}
				Swap(array[begin], array[end]);
		}
		array[begin] = pv;
		return begin;
}

/******************************************
 * 快速排序功能函数
 * array: 序列
 * begin:序列的起始位置
 * end: 序列的结束位置
 * min2max:按从小到大进行排序
 * ***************************************/
template <typename T>
static void Quick(T array[], int begin, int end, bool min2max)
{
		if(begin < end)
		{
			//对序列进行区域划分
			int pivot = Partition(array, begin, end, min2max);
			//对基准左侧的区域进行快排序
			Quick(array, begin, pivot, min2max);
			//对基准右侧的区域进行块排序
			Quick(array, pivot+1, end, min2max);
		}
}
/******************************************
* 排序方式:快速排序
* array: 序列
* len:序列中元素个数
* min2max:按从小到大进行排序
* ***************************************/
template <typename T>
static void Quick(T array[], int len, bool min2max=true)
{
	Quick(array, 0, len-1, min2max);
}

快速排序通过递归的方式对排序问题重新划分,是一种不稳定的排序方法,时间复杂度为O(nlogn)。

八、排序的工程应用

1、排序算法面向数组类的实现

template <typename T>
 static void Select(Array<T>& array, bool min2max=true)
 {
     Select(array.array(), array.length(), min2max);
 }

 template <typename T>
 static void Insert(Array<T>& array, bool min2max=true)
 {
     Insert(array.array(), array.length(), min2max);
 }

 template <typename T>
 static void Bubble(Array<T>& array, bool min2max=true)
 {
     Bubble(array.array(), array.length(), min2max);
 }

 template <typename T>
 static void Shell(Array<T>& array, bool min2max=true)
 {
     Shell(array.array(), array.length(), min2max);
 }

 template <typename T>
 static void Merge(Array<T>& array, bool min2max=true)
 {
     Merge(array.array(), array.length(), min2max);
 }

 template <typename T>
 static void Quick(Array<T>& array, bool min2max=true)
 {
     Quick(array.array(), array.length(), min2max);
 }

2、大规模序列的排序问题

排序过程中不可避免的需要进行交换操作,交换操作的本质为数据元素间的相互复制,如果序列的规模巨大时,交换操作将耗时巨大。 通过使用代理模式,为待排序元素设置代理对象,对代理对象组成的序列进行排序,需要访问有序序列元素时,通过访问待序列完成。 通过使用空间换时间提高算法效率。