数据结构伴内排序算法:直接排序、折半排序、希尔排序、冒泡排序、快速排序、简单选择排序、堆排序、归并排序、基数排序

  • 一、插入排序
  1. 直接插入排序
  2. 折半插入排序
  3. 希尔排序
  • 二、交换排序
  1. 冒泡排序
  2. 快排序
  • 三、选择排序
  1. 简单选择排序
  2. 堆排序
  • 四、归并排序
  • 五、基数排序

图 ∞    各种排序方法的性能

适用于小规模数据:直接插入排序、冒泡排序、简单选择排序

适用于大规模数据希尔排序、

适用于各种规模数据:快速排序、堆排序、二路归并排序

适用于非负数数据基数排序

一、插入排序

1.1 直接插入排序

直接插入排序(straight insertion sort):一趟操作时将当前无序区的开头元素 R[i](1\leq i\leq n-1) 插入到有序区 R[0...i-1] 中的适当位置,使 R[0...i]变为新的有序区。如图1.1。这种方法叫做增量法,因为它每次使有序区增加一个元素。

举例如图 1.2:

  • 算法如下:
#include<iostream>
using namespace std;

void InsertSort(int R[], int n) // 对 R[0...n-1]按递增有序进行直接插入排序
{
	int i, j, tmp;
	for(i = 1; i < n; i++){
		if(R[i] < R[i-1])	    // 反序时
		{
			tmp = R[i];
			j = i-1;
			do				    // 找R[i]的插入位置
			{
				R[j+1] = R[j];	// 将大于R[i]的元素后移
				j--;
			}while(j >= 0 && R[j] >tmp);
			R[j+1] = tmp;		// 在j+1处插入R[i]
		}
	}
}
int main()
{
  	int R[] = {9,8,7,2,5,0,3,6,1,4};
	int i, n = sizeof(R)/sizeof(R[0]);
	InsertSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   return 0;
}

 运行结果:

0 1 2 3 4 5 6 7 8 9 

例题

  1. 从未排序序列中依次取出元素与已排序序列中的元素进行比较,将其放入已排序序列的正确位置上的方法。

1.2 折半插入排序

        直接插入排序中将无序区的开头元素R[i](1\leq i\leq n-1)插入到有序区R[0...i-1]是采用顺序比较的方法。由于有序区的元素是有序的,可采用折半查找方法先在R[0...i-1]中找到插入位置,再通过移动元素进行插入,这样的插入排序称为折半插入排序(binary insertion sort)或二分插入排序

思路:第 i 趟在[low...high](初始时 low=0,high=i-1)中采用折半查找方法插入 R[i]的位置为R[high+1],再将R[high+1...i-1]元素后移一个位置,并置R[high+1]=R[i]

说明:和直接插入排序一样,折半插入排序每趟产生的有序区并不一定是全局有序区。

  • 算法如下:
#include<iostream>
using namespace std;

void BinInsertSort(int R[], int n) // 对 R[0...n-1]按递增有序进行直接插入排序
{
	int i, j, low, high, mid, tmp;
	for(i = 1; i < n; i++){
		if(R[i] < R[i-1])	// 反序时
		{
			tmp = R[i];		// 将R[i]保存到tmp中
			low = 0; high = i-1;
			while(low <= high)			// 在R[low...high]中查找插入的位置
			{
				mid = (low+high) / 2;	// 取中间位置
				if(tmp < R[mid])
					high = mid-1;		// 插入点在左半区
				else
					low = mid+1;		// 插入点在右边区
			}
			for(j = i-1; j >= high+1; j--)	// 集中进行元素后移
				R[j+1] = R[j];
			R[high+1] = tmp;				// 插入 tmp
		}
	}
}
int main()
{
  	int R[] = {9,8,7,2,5,0,3,6,1,4};
	int i, n = sizeof(R)/sizeof(R[0]);
	BinInsertSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   return 0;
}

 运行结果:

0 1 2 3 4 5 6 7 8 9 

1.3 希尔排序

希尔排序(shell sort):也是一种插入排序,也可称为缩小增量排序,实际上是一种分组插入策略。希尔排序是把待排序数据(例如,实数)按下标的“一定”增量分组,对每组使用直接插入排序算法进行排序;随着增量逐渐减小,每组所包含的数据越来越多,当增量减小到 1 时,整个数据恰好被分为一组,算法终止。

注意希尔排序算法 不能保证每趟排序至少能将一个元素放大其最终位置上。

思路:

1)先取一个小于 n 的整数 d_1 (增量因子)作为第一个增量,把表的全部元素分成 d_1 个组,将所有距离为 d_1 的倍数的元素放在同一个组中,图 1.5 是分为 d 组的情况(每组分得 k <=\left \lceil n/d \right \rceil 图 1.4)。

2)在各组内进行直接插入排序;

3)然后取第2个增量  d_2(<d_1),重复上述的分组和排序,

4)直到所取的增量d_t=1(d_t<d_{t-1}<...<d_2<d_1 ),即所有元素放在同一组中进行直接插入排序为止。所以希尔排序称为减少增量的排序方法。

5)每一趟希尔排序从元素R[d]开始起,采用直接插入排序,直到元素 R[n-1]为止。每一个元素的比较和插入都在同组内部进行,对于元素R[i],同组的前面的元素有\{R[j] | j = i-d >=0\}

好的增量因子特点:① 一般的初次序列的一半为增量,以后每次减半,直到增量为 1

② 应该尽量避免增量序列中的值(尤其是相邻的值)互为倍数的情况。

        说明:希尔排序每趟并不产生有序区,在最后一趟排序结束前,所有元素并不一定归位了,但是在希尔排序每趟完成后数据越来越接近有序。

举例如图 1.6:

  • 算法如下:
#include<iostream>
using namespace std;

void ShellSort(int R[], int n) // 对 R[0...n-1]按递增有序进行直接插入排序
{
	int i, j, d;
	int tmp;
	d = n/ 2;						// 增量置初值
	while(d > 0)
	{
		for(i = d; i < n; i++)		// 对所有采用直接插入排序
		{
			tmp = R[i];				// 对相隔d个位置一组采用直接插入排序
			j = i - d;
			while(j >= 0 && tmp < R[j])
			{
				R[j+d] = R[j];
				j = j - d;
			}
			R[j+d] = tmp;
		}
		d = d / 2;				// 减小增量
	}
}
int main()
{
  	int R[] = {9,8,7,2,5,0,3,6,1,4};
	int i, n = sizeof(R)/sizeof(R[0]);
	ShellSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9 

二、交换排序

2.1 冒泡排序

        冒泡排序(bubble sort):也称为气泡排序,是一种典型的交换排序方法,其基本思想是通过无序区中相邻元素关键字间的比较和位置的交换使关键字最小的元素如气泡一般逐渐网上“漂浮”直至“水面”。       

思路:整个算法从最下面的元素开始,对每个相邻的关键字进行比较,且使关键字较小的元素换至关键字较大的元素之上,使得经过一趟冒泡排序后关键字最小的元素到达最上端,如图 1.7所示。接着,在剩余的元素中找关键字次小的元素,并把它换至第二个位置上。以此类推,直到所有元素都有序为止。

例子如图 1.8 所示

算法:

#include<iostream>
using namespace std;

void swaps(int &i, int &j)
{
	int tmp;
	tmp = i, i = j, j = tmp;
}
void BubbleSort(int R[], int n)
{
	int i, j;
	for(i = 0; i < n-1; i++)
		for(j = n-1; j > i; j--)	// 将R[i]元素归位
			if(R[j] < R[j-1])		// 相邻元素反序使
				swaps(R[j], R[j-1]);	// 两元素互换
}
int main()
{
	int i, n;
	int R[] = {9,8,7,2,5,0,3,6,1,4};
	n = sizeof(R) / sizeof(R[0]);
	BubbleSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
   return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

优化部分:

        在某些情况下,在第i(i<n-1)趟时已经排好了,担仍然执行后面几趟的比较。实际上,一旦算法中的某一趟比较时不出现任何元素交换,说明已排好序了,就可以介绍本算法。为此,改进冒泡排序算法如下:

#include<iostream>
using namespace std;
 
void swaps(int &i, int &j)
{
	int tmp;
	tmp = i, i = j, j = tmp;
}
void BubbleSort(int R[], int n)
{
	int i, j;
	bool exchange;
	for(i = 0; i < n-1; i++)
	{
		exchange = false;			// 一趟前置为假
		for(j = n-1; j > i; j--)	// 将 R[i]元素归位,循环 n-i-1次 
			if(R[j] < R[j-1])		// 相邻元素反序使
			{
				swaps(R[j], R[j-1]);	// 两元素互换
				exchange = true;	// 一旦有交换,将其置为真 
			}
		if(!exchange)				// 本趟没有发生交换,中途结束算法 
			return ;
	}
}
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	BubbleSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
   return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

例题:

  1. 对 n 个不同的排序码进行冒泡排序,在元素无序的情况下比较的次数最多为 n(n-1)/2

2.2 快排序

        快速排序(quick sort):是由冒泡排序改进得来的,它的基本思想是分而治之。

  • 在待排序的n个元素中任取一个元素(通常取第一个元素)作为基准,把该元素放在适当的位置后,数据序列被此元素划分成两部分。所有关键字比该元素关键字小的元素放置在前一部分,所有比它大的元素放置在后一部分,并把该元素排在这两部分中间(称为该元素归位),这个过程称为一趟快速排序,即一趟划分。
  • 之后对产生的两个部分分别重复上述过程,直至每个部分内只有一个元素或空为止。简而言之,每趟划分时表的第一个元素放在适当位置,将表一分为二,对子表按递归方式继续这种划分,直至划分的子表长的为1或0。

算法一:

        一趟快速排序的划分过程 partition(R,s,t)是采用从两头想中间扫描的办法,同时交换与基准元素逆序的元素。

  1. 具体方法是设两个指示器i和j,它们的初值分别指向无序区中的第一个和最后一个元素。
  2. 假设无序区中的元素为R[s]\,,R[s+1]\,,...\,,R[t],则i的初值为s,j的初值为t,首先将R[s]移至变量tmp中作为基准,令j自位置t起向前扫描直至R[j]<tmp时将R[j]移至位置i,然后让i向后扫描直至R[i]>tmp时将R[i]移动至位置j,
  3. 依此类推,直至i=j,
  4. 此时所有R[k](k=s,s+1,...,i-1)的关键字都小于tmp,而所有R[k](k=i+1,...,t)的关键字必大于tmp,此时再将tmp中的元素移至位置i,
  5. 它将无序区中的元素分割成R[s...i-1]R[i+1...high],以便分别进行排序,如图 2.4所示。

例题 如图 2.3所示:

注意:上述例题分析,对单个关键字(数值)也进行了一次递归,实际情况可以单个关键字不进行递归,仅需一个 if 就可实现。若将算法实现一种QuickSort()函数中的 if 条件改为 i<=j,则递归 n次。

算法实现一:

#include<iostream>
using namespace std;

int partition(int R[], int s, int t)	// 一趟划分 
{
	int i = s, j = t;
	int tmp = R[i];						// 以 R[i]为基准 

	while(i < j)			// 从两端交替向中间扫描,直至 i= j 为止
	{
		while(j > i)		// 从左向右扫描,找到一个小于 tmp的 R[j] 
		{
			if(R[j] < tmp)
				break;
			j--;
		}			
		R[i] = R[j];		// 将找的的 R[j]放入 R[i]中
		
		while(i < j)		// 从左向右扫描,找到一个大于 tmp的 R[i] 
		{
			if(R[i] > tmp)
				break;
			i++;
		}
		R[j] = R[i];		// 找到这样的R[i],放入 R[j]中 
	}
	R[i] = tmp;
	return i;
}
void QuickSort(int R[], int s, int t)	// 对 R[s...t]的元素进行快速排序
{
	int i;
	if(s < t) 				// 区间内至少存在两个元素的情况
	{
		i = partition(R, s, t);
		QuickSort(R, s, i-1);	// 对左区间递归排序
		QuickSort(R, i+1, t);	// 对右区间递归排序 
	}
 } 
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	QuickSort(R, 0, n-1);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
   return 0;
}

运算结果:

0 1 2 3 4 5 6 7 8 9

共递归6次。

优化:实际上,在快速排序中可以以任意一个元素为基准(更好的选择方法是从数字中随机选择一个元素作为基准),以下算法以当前区间的中间位置为元素为基准,同样可以达到快速排序的目的。

算法实现二:

#include<iostream>
using namespace std;
static int count = 0; 
int partition(int R[], int s, int t)	// 一趟划分 
{
	int i = s, j = t;
	int tmp = R[i];						// 以 R[i]为基准 
 
	while(i < j)			// 从两端交替向中间扫描,直至 i= j 为止
	{
		while(j > i)		// 从左向右扫描,找到一个小于 tmp的 R[j] 
		{
			if(R[j] < tmp)
				break;
			j--;
		}			
		R[i] = R[j];		// 将找的的 R[j]放入 R[i]中
		
		while(i < j)		// 从左向右扫描,找到一个大于 tmp的 R[i] 
		{
			if(R[i] > tmp)
				break;
			i++;
		}
		R[j] = R[i];		// 找到这样的R[i],放入 R[j]中 
	}
	R[i] = tmp;
	return i;
}
void swaps(int &i, int &j)
{
	int tmp;
	tmp = i, i = j, j = tmp;
}
void QuickSort(int R[], int s, int t)	// 对 R[s...t]的元素进行快速排序
{
	int i, pivot;
	pivot = s + (t-s)/2;		// 用区间中间元素作为基准 
	if(s < t) 					// 区间内至少存在两个元素的情况
	{
		++count;
		if(pivot != s)			// 若基准不是区间中的第一个元素,将其与第一个元素交换 
			swaps(R[pivot], R[s]); 
		i = partition(R, s, t);
		QuickSort(R, s, i-1);	// 对左区间递归排序
		QuickSort(R, i+1, t);	// 对右区间递归排序 
	}
 } 
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	QuickSort(R, 0, n-1);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   cout << '\n' << count;
   return 0;
}

运算结果:

0 1 2 3 4 5 6 7 8 9
8

        显然算法二的递归多与算法一的递归次数,这告诉我们算法的优进并不都是我们所期待的。

算法实现三:

#include<iostream>
using namespace std;

void swaps(int &i, int &j)
{
	int tmp;
	tmp = i; i = j; j = tmp;
}
void QuickSort2(int R[], int low, int high)
{
	if(low >= high) return ;
	int i = low, j = high;
	int key = R[low];
	while(true)
	{
		while(R[i] <= key)		// 从左向右找比 key大的值
		{
			i++;
			if(i == high)
				break;
		}
		while(R[j] >= key)		// 从右向左找比 key小的值
		{
			j--;
			if(j == low)
				break;
		}
		if(i >= j) break;
		swaps(R[i], R[j]);
	}
	// 中枢值与 j对应值交换
	R[low] = R[j];
	R[j] = key;
	QuickSort2(R, low, j-1);
	QuickSort2(R, j+1, high); 
}
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	QuickSort2(R, 0, n-1);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
   return 0;
}

运算结果:

0 1 2 3 4 5 6 7 8 9

共递归6次。

例题

  1. 快速排序在 被排序的数据完全无序 情况下最易发挥其长处。

三、选择排序

3.1 简单选择排序

        简单选择排序(simple selection sort):每一趟从待排序的元素中选出关键字最小的元素,顺序放在已排好的子表后的最后,直到全部元素排序完毕。由于选择排序方法每趟总是从无序区中选出全局最小(或最大)的关键字,所以适合于从大量元素中的选择一部分排序元素。

思路

  1. 第 i 趟排序开始,当前有序区和无序区分别为R[0...i-1]R[i...n-1](0<=i<=n-1),该趟排序是从当前无序区中选出关键字最小元素R[i],将它变为新的有序区的第 1 个元素R[i]交换,使R[0...i]R[i+1...n-1]分别为新的有序区和新的无序区,如图 3.1 所示。
  2. 因为每趟排序均使有序区中增加了一个元素,且有序区中元素的关键字均不大于无序区中元素的关键字,即第 i 趟排序之后R[0...i]的所有关键字均小于等于R[i+1...n-1]中的所有关键字,所以进行 n-1趟排序之后R[0...n-2]的所有关键字小于等于R[n-1],也就是说,经过 n-1 趟排序之后整个表 R[0...n-1]递增有序。

说明:简单选择排序每趟产生的有序区一定是全局有序,也就是说,每趟产生的有序区中所有元素都归位了。

举例 如图 3.2 所示:

算法实现:

#include<iostream>
using namespace std;

void swaps(int &i, int &j)
{
	int tmp;
	tmp = i, i = j, j = tmp;
}
void SelectSort(int R[], int n)
{
	int i, j, k;
	for(i = 0; i < n-1; i++)		// 做第 i 趟排序
	{
		k = i;
		for(j = i+1; j < n; j++)	// 在当前无序区 R[i...n-1]中选 key 最小的 R[k]
			if(R[j] < R[k])
				k = j;				// k 记下目前最小的关键字所在的位置
		if(k != i)
			swaps(R[i], R[k]); 
	}
}
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	SelectSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
	return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

例题

  1. 从未排序序列中挑选元素,将其依次插入已排序序列(初始时为空)的一端的方法。

3.2 堆排序

        堆排序(heap sort):是一种树形选择排序,它的特点是将 R[1..n](R[i]的关键字为k_i)看成是一颗完全二叉树的顺序结构,如图 3.4 所示。利用完全二叉树中双亲结点和孩子结点之间的位置关系在无序区中选择关键字最大(或最小)的元素。

        堆的定义是 R[1...n] 中的 n 个关键字序列k_1,k_2,...,k_n称为堆,当且仅当序列满足如下性质(简称为堆的性质):

  1. k_i\leq k_{2i}k_i\leq k_{2i+1}
  2. k_i\geq k_{2i}k_i\geq k_{2i+1} (1\leq i\leq \left \lfloor n/2 \right \rfloor)

        满足第 1 种情况的堆称为小根堆(对于图 3.5,就是树中分支任何结点的关键字都小于其孩子结点的关键字),满足第 2 种情况的堆称为大根堆(对于图 3.5,就是树中任何分支结点的关键字都大于等于其孩子结点的关键字)。下面主要讨论大根堆。

        堆排序的排序过程与简单选择排序类似,只是挑选最大或最小元素时采用的方法不同,这里采用大根堆,每次挑选最大元素归位,排序如图 3.6 所示。挑选最大元素是采用筛选的方法实现的。

算法实现:

#include<iostream>
using namespace std;

void swaps(int &i, int &j)
{
	int tmp;
	tmp = i, i = j, j = tmp;
}
void sift(int R[], int low, int high)
{
	int i = low, j = 2 * i;		// R[j] 是 R[i] 的孩子
	int tmp = R[i];
	while(j <= high)
	{
		if(j < high && R[j] < R[j+1])	// 若右孩子较大,把 j 指向右孩子
			j++;
		if(tmp < R[j])			// 若根结点小于最大孩子的关键字
		{
			R[i] = R[j];		// 将 R[i] 调整到双亲结点的位置 
			i = j;				// 修改 i 和 j的值,以便继续向下筛选 
			j = 2 * i;
		} else
			break;				// 若根结点大于等于最大孩子关键字,筛选结束 
	}
	R[i] = tmp;					// 被筛选结点放入最终位置上 
}
void HeapSort(int R[], int n)
{
	int i;
	for(i = n/2; i >= 1; i--)	// 循环建立初始堆,调用sift算法 n/2 下界次
		sift(R, i, n);
	for(i = n; i >= 2; i--)		// 进行 n-1 趟完成堆排序,每一趟堆中元素个数减一
	{
		swaps(R[1], R[i]);		// 将最后一个元素与根 R[1] 交换
		sift(R, 1, i-1);		// 对 R[1...i-1] 进行筛选,得到 i-1 个结点的堆 
	}
}
int main()
{
	int i, n;
	int R[] = {1000,1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	HeapSort(R, n-1);
	for(i = 1; i < n; i++)
		cout << R[i] << " ";
   
	return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

例题

  1. 数据表中有 10000个元素,如果仅要求求出其中最大的 10 个元素,采用 堆排序算法 最节省时间。

四、归并排序

        归并排序(merge sort):是多次将两个或两个以上的有序表合并成一个新的有序表。最简单的归并是直接将两个有序的子表合并成一个有序表,即二路归并

        二路归并排序(2-way merge ):基本思路是将R[0...n-1]看成是 n 个长度为 1 的有序序列,然后进行两两归并,得到\left \lceil n/2\right \rceil个长度为 2 (最后一个有序序列的长度可能为 2)的有序序列,再进行两两归并,得到\left \lceil n/4\right \rceil个长度为 你的有序序列。

        说明:归并排序每趟产生的有序区只是局部有序的,也就是说在最后一趟排序结束前所有元素并不一定归位了。

算法实现:

#include<iostream>
#include<stdlib.h>
using namespace std;

void merge(int R[], int low, int mid, int high)	// 归并 R[low...high]
{
	int *R1;
	int i = low, j = mid + 1, k = 0;	// k 是 R1 的下标,i、j分别为第 1、2段的下标
	R1 = (int *)malloc((high-low+1)*sizeof(int));	// 动态分配空间
	while(i <= mid && j <= high)			// 在第 1 段和第 2 段均为扫描完时循环
		if(R[i] <= R[j])					// 将第 1 段中的元素放入 R1 中
			R1[k++] = R[i++];
		else							// 将第 2 段中的元素放入 R1 中 
			R1[k++] = R[j++];
	while(i <= mid)				// 将第 1 段余下的元素放入 R1 中
		R1[k++] = R[i++];
	while(j <= high)			// 将第 2 段余下的元素放入 R1 中
		R1[k++] = R[j++];
	for(k = 0, i = low; i <= high; )	// 将 R1 复制到 R[low...high]
		R[i++] = R1[k++]; 
	free(R1);
}
void MergePass(int R[], int length, int n)		// 对整个排序进行一趟归并
{
	int i;
	for(i=0; i+2*length-1 < n; i += 2*length)	// 归并 length 长的相邻子表
		merge(R, i, i+length-1, i+2*length-1);
	if(i+length-1 < n-1)						// 余下两个子表,后者的长度小于 length
		merge(R, i, i+length-1, n-1);			// 归并这两个子表 
}
void MergeSort(int R[], int n)					// 二路归并排序 
{
	int length;
	for(length = 1; length < n; length *= 2)	// 进行 |''log_2(n)''| 趟排序
		MergePass(R, length, n);
}
int main()
{
	int i, n;
	int R[] = {1,8,7,2,5,0,9,6,3,4};
	n = sizeof(R) / sizeof(R[0]);
	MergeSort(R, n);
	for(i = 0; i < n; i++)
		cout << R[i] << " ";
   
	return 0;
}

运行结果:

0 1 2 3 4 5 6 7 8 9

五、基数排序

    前面所讨论的排序算法均是基于关键字之间的比较来实现的,而基数排序(radix sort)是通过“分配”和“收集”过程来实现排序,不需要进行关键字间比较,是一种借助于多关键字排序的思想对关键字排序的方法。

  •         一般情况下,元素 R[i] 的关键字 R[i] 由 d 位数字(或字符)组成,即 k^{d-1} k^{d-2}\cdot \cdot \cdot k^1 k^0,每一个数字表示关键字的一位,其中 k^{d-1} 是最高位、k^0 是最低位,每一位的值都在 0\leq k^i<r范围内,其中 r 称为基数(radix)。例如,对于二进制数 r 为 2,对于十进制数 r 为 10。
  •        最低位优先的过程是先按最地位的值对元素进行排序,在此基础上再次按低位进行排序,以此类推。由低位向高位,每趟都是根据关键字的一位并在前一趟的基础上对所有元素进行排序,直至最高位,则完成了基数排序的完整过程。
  •        在对一个数据序列排序时是采用最低位优先还是最高位优先排序方法是由数据序列的特点确定的。例如对整数序列递增排序,由于个位数的重要性对于十位数,十位数的重要性1低于百位数,一般越重要的位数放在后面排序,个位数属于最低位,所以对整数序列递增排序时应采用最低位优先排序方法。

    说明:基数排序每趟并不产生有序区,也就是说在最后一趟排序结束前所有元素并不一定归位了。

举例讲解 如图 5.1 所示。

代码实现:

// 最低位优先的基数排序算法
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#define MAXE 20			// 线性表中最多元素个数
#define MAXR 10			// 基数的最大取值
#define MAXD 8			// 关键字位数的最大取值
typedef struct node
{	
	char data[MAXD];	// 记录的关键字定义的字符串
    struct node *next;
} NodeType;
void CreaLink(NodeType *&p,char *a[],int n);
void DispLink(NodeType *p);
void RadixSort(NodeType *&p,int r,int d) // 实现基数排序:p为待排序序列链表指针,r为基数,d为关键字位数
{
	NodeType *head[MAXR],*tail[MAXR],*t;// 定义各链队的首尾指针
	int i,j,k;
	for (i=0;i<=d-1;i++)           		// 从低位到高位循环
	{	
		for (j=0;j<r;j++) 				// 初始化各链队首、尾指针
			head[j]=tail[j]=NULL;
		while (p!=NULL)          		// 对于原链表中每个结点循环
		{	
			k=p->data[i]-'0';   		// 找第k个链队
			if (head[k]==NULL)   		// 进行分配
			{
				head[k]=tail[k]=p;
			}else{
              	tail[k]->next=tail[k]=p;
			}
            p=p->next;             		// 取下一个待排序的元素
		}
		p=NULL;							// 重新用p来收集所有结点
       	for (j=0;j<r;j++)        		// 对于每一个链队循环
        	if (head[j]!=NULL)         	// 进行收集
			{	
				if (p==NULL){
					p=head[j];
					t=tail[j];
				}else{
					t->next=head[j];
					t=tail[j];
				}
			}
		t->next=NULL;					// 最后一个结点的next域置NULL
		printf("  按%s位排序\t",(i==0?"个":"十"));
		DispLink(p);
	}
}
void CreateLink(NodeType *&p,char a[MAXE][MAXD],int n)	// 采用后插法产生链表
{
	int i;
	NodeType *s,*t;
	for (i=0;i<n;i++)
	{
		s=(NodeType *)malloc(sizeof(NodeType));
		strcpy(s->data,a[i]);
		if (i==0){
			p=s;t=s;
		}else{
			t->next=s;t=s;
		}
	}
	t->next=NULL;
}
void DispLink(NodeType *p)	// 输出链表
{
	while (p!=NULL)
	{
		printf("%c%c ",p->data[1],p->data[0]);
		p=p->next;
	}
	printf("\n");
}
int main()
{
	int n=10,r=10,d=2;
	int i,j,k;
	NodeType *p;
	char a[MAXE][MAXD];
	int b[]={75,23,98,44,57,12,29,64,38,82};
	for (i=0;i<n;i++)		// 将 b[i]转换成字符串
	{
		k=b[i];
		for (j=0;j<d;j++)	// 例如 b[0]=75,转换后 a[0][0]='7',a[0][1]='5'
		{
			a[i][j]=k%10+'0';
			k=k/10;
		}
		a[i][j]='\0';
	}
	CreateLink(p,a,n);
	printf("\n");
	printf("  初始关键字\t");		// 输出初始关键字序列
	DispLink(p);
	RadixSort(p,10,2);
	printf("  最终结果\t");			// 输出最终结果
	DispLink(p);
	printf("\n");
}

运算结果


  初始关键字    75 23 98 44 57 12 29 64 38 82
  按个位排序    12 82 23 44 64 75 57 98 38 29
  按十位排序    12 23 29 38 44 57 64 75 82 98
  最终结果      12 23 29 38 44 57 64 75 82 98

2024我们卓厉奋发,砥砺前行,畅谈23,圆梦24。后续完善中

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值