排序系列(九)基数排序

基数排序
基数排序是桶排序的一种推广, 它所考虑的待排记录包含不止一个关键字。
例如对一副牌的整理,可将每张牌看作一个记录,包含两个关键字:花色、面值。
一副理顺的牌是按如下顺序排放的:
♣2…A♣,◆2…A◆,♥2…A♥,♠2…A♠
可见一个有序列是先按花色划分为四大块,每一块中又再按面值大小排序。
这时“花色”就是一张牌的“最主位关键字”,而“面值"是“最次位关键字”。
对于一般有K个关键字的情况,基数排序通常有两种方法:主位优先法(Most SignificantDig-it Sort, 简称MSD)
和次位优先法( Least Significant Digit Sort,简称LSD)。
仍以整理扑克牌为例,顾名思义,所谓主位优先法,是先为最主位关键字(花色)建立桶,
将牌按花色分别装进4个桶里;然后对每个桶中的牌,再按其次位关键字(面值)进行排序,
最后将4个桶中的牌收集,顺序叠放在一起。
而次位优先法,是先为最次位关键字建立桶,即按面值建立13个桶,将牌按面值分别放于13个桶中;
然后将所有桶中的牌收集,顺序叠放在一起;
再为主位关键字花色建立4个桶,顺序将每张牌放人对应的花色桶中,则4个花色桶中的牌必是有序的,
最后只要将它们收集,顺序叠放即可。
从上述例子可见,两种方法具有不同的特点。
主位优先法基本上是分治法的思路,将序列分割成子列后,分别排序再合并结果。
而次位优先法是将“排序"过程分解成了“分配”和“收集”这两个相对简单的步骤,并不需要分割子列排序,故一般情况下次位优先法的效率更高一些。

基数分解
从上面可以看到,基数排序主要是对有多关键字的对象进行排序。
其实可以将单个整型关键字按某种基数分解为多关键字,再进行排序。这也是“基数排序”名称的由来。
例如826可以根据基数10分解为8、2、6这三个关键字,其中8是最主位关键字,6是最次位关键字;
还可以根据基数16分解为3.3、A这3个关键字,其中第一个3是最主位关键字,A是最次位关键字。
典型问题是给定N个记录,每个记录的关键字为一个整数,其取值范围在0到M之间。若M比N大很多(例如M=N*),这时桶排序需要M个桶,会造成巨大的空间浪费;
而以R为基数对关键字进行分解后则只需要R个桶就可以了。

算法实现-次位优先法(C#):


// 关键词最大个数

private static readonly int MaxDigit = 3;



// 分解基数  基数和关键词最大个数需根据实际情况选取

private static readonly int Radix = 10;



// 桶节点

private class BucketNode

{

public int Key { get; set; }

public BucketNode Next { get; set; }

}



// 桶头节点

private class HeadBucketNode

{

public BucketNode Head { get; set; }

public BucketNode Tail { get; set; }

}



// 获取元素指定位序关键词 位序从1开始 1=最次位关键字,maxDigit-1=最主位关键字

private static int GetNumberDigit(int key, int digit)

{

int result = 0;

for (int i = 1; i <= digit; i++)

{

	result = key % Radix;

	key /= 10;

}

return result;

}



// 次位排序

public static void LSDRadixSort(int[] arr)

{

// 初始化变量

// 分解关键词

int subKey;

BucketNode head = null;

BucketNode tmp = null;

BucketNode p = null;

HeadBucketNode[] buckets;

// 初始化桶

buckets = new HeadBucketNode[Radix];

for (int i = 0; i < Radix; i++)

{

	buckets[i] = new HeadBucketNode()

	{

		Head = null,

		Tail = null

	};

}



// 将待排序数组倒序/顺序进入链表

// N

for (int i = 0; i < arr.Length; i++)

{

	tmp = new BucketNode();

	tmp.Key = arr[i];

	tmp.Next = head;

	head = tmp;

}



// 按关键词位序进行排序  从最次位关键字到最主位关键字,顺序不可逆

for (int keyIndex = 1; keyIndex <= MaxDigit; keyIndex++)

{

	// 将元素分配到各个桶中

	// 根据位序分解关键词

	p = head;

	// N

	while (p != null)

	{

		subKey = GetNumberDigit(p.Key, keyIndex);

		tmp = p;

		p = p.Next;

		tmp.Next = null;



		if (buckets[subKey].Head == null)

		{

			buckets[subKey].Head = tmp;

			buckets[subKey].Tail = tmp;

		}

		else

		{

			buckets[subKey].Tail.Next = tmp;

			buckets[subKey].Tail = tmp;

		}

	}



	//  M

	// 将元素收集入链表

	head = null;

	for (subKey = Radix - 1; subKey >= 0; subKey--)

	{

		if (buckets[subKey].Head != null)

		{

			buckets[subKey].Tail.Next = head;

			head = buckets[subKey].Head;

			buckets[subKey].Head = null;

			buckets[subKey].Tail = null;

		}

	}

}



// 将已排序好的链表复制回数组

for (int i = 0; i < arr.Length; i++)

{

	arr[i] = head.Key;

	head = head.Next;

}

}

对N个关键字用R个桶进行基数排序时,其时间复杂度为0(D(N+R)),其中D为分配收集的趟数,
也就是关键字按基数分解后的位数。当记录的个数N与桶的个数基本是同一数量级时,基数排序可以达到线性复杂度。
但注意到由于链表指针操作的引入,这个0(N)的常数项可能会超过logN,从而实际效果也未必比前面讲过的几种算法要好很多。
另一方面,基数排序用链表实现的好处是不需要将记录进行物理移动,对于大型记录的排序是有利的,代价是需要O(N)额外空间存放指针。
总之,基数排序的效率与基数的选择密切相关,而基数的选择需要综合考虑待排记录的规模和关键字的取值范围。

算法实现-主位优先法(C#):


// 基数排序-主位优先法

public static void MSDRadixSort(int[] arr)

{

// 初始化桶

int mainKey;

int subKey;

BucketNode tmp;

BucketNode headTmp;

HeadBucketNode[] buckets = new HeadBucketNode[Radix];

HeadBucketNode[] subBuckets = new HeadBucketNode[Radix];

for (int i = 0; i < buckets.Length && i < subBuckets.Length; i++)

{

	buckets[i] = new HeadBucketNode()

	{

		Head = null,

		Tail = null

	};



	subBuckets[i] = new HeadBucketNode()

	{

		Head = null,

		Tail = null

	};

}



// 按主关键词进行桶的分配

for (int i = 0; i < arr.Length; i++)

{

	mainKey = GetNumberDigit(arr[i], MaxDigit);

	tmp = new BucketNode();

	tmp.Key = arr[i];

	tmp.Next = null;



	if (buckets[mainKey].Head == null)

	{

		buckets[mainKey].Head = tmp;

		buckets[mainKey].Tail = tmp;

	}

	else

	{

		buckets[mainKey].Tail.Next = tmp;

		buckets[mainKey].Tail = tmp;

	}

}



// 对桶内关键词进行排序

// 处理所有次位关键词

for (int d = 1; d < MaxDigit; d++)

{

	for (int i = 0; i < buckets.Length; i++)

	{

		headTmp = buckets[i].Head;

		while (headTmp != null)

		{

			// 分配

			subKey = GetNumberDigit(headTmp.Key, d);

			tmp = new BucketNode();

			tmp.Key = headTmp.Key;

			tmp.Next = null;

			if (subBuckets[subKey].Head == null)

			{

				subBuckets[subKey].Head = tmp;

				subBuckets[subKey].Tail = tmp;

			}

			else

			{

				subBuckets[subKey].Tail.Next = tmp;

				subBuckets[subKey].Tail = tmp;

			}

			headTmp = headTmp.Next;

		}



		// 收集

		buckets[i].Head = null;

		buckets[i].Tail = null;

		for (int j = subBuckets.Length - 1; j >= 0; j--)

		{

			if (subBuckets[j].Head != null)

			{

				subBuckets[j].Tail.Next = buckets[i].Head;

				buckets[i].Head = subBuckets[j].Head;

				if (buckets[i].Tail == null)

					buckets[i].Tail = subBuckets[j].Tail;

				subBuckets[j].Head = null;

				subBuckets[j].Tail = null;

			}

		}

		PrintBucketLinkList(buckets[i].Head);

	}

}



// 收集

headTmp = null;

for (int i = buckets.Length - 1; i >= 0; i--)

{

	if (buckets[i].Head != null)

	{

		buckets[i].Tail.Next = headTmp;

		headTmp = buckets[i].Head;

		buckets[i].Head = null;

		buckets[i].Tail = null;

	}

}

// 将排序后的序列复制回数组

for (int i = 0; i < arr.Length; i++)

{

	arr[i] = headTmp.Key;

	headTmp = headTmp.Next;

}

}

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值