您的位置:首页 > 理论基础 > 数据结构算法

数据结构--排序算法(归并排序&&基数排序&&桶排序)

2017-03-24 16:52 204 查看
在前面我们说了快速排序等排序方法,如有同学想看则请查阅往期博:

插入排序,选择排序:http://blog.csdn.net/sayhello_world/article/details/61927082

冒泡排序,快速排序,鸽巢排序:http://blog.csdn.net/sayhello_world/article/details/63258406

今天我们来说归并排序

归并排序:

递归归并排序:

思路:归并,是将两个或两个以上的有序表合并成一个新的有序表。通过逐层划分,最后逐层向上递归排序,最后得到一个有序的数组。



归并排序的思想及特点:

采用分而治之(divide and conquer)的策略。 

小的数据表排序比大的数据表要快。 

从两个已经排好序的数据表中构造一个排好序的数据表要比从两个未排序的书记表中构造要少许多步骤。 

它是一个稳定的排序算法。

时间复杂度:O(nLogn)

递归实现:

bool Merge_Sort(int array[], int size)
{
int *temp = new int[size];
if (temp == NULL)
return false;

MergeSort(array, temp, 0, size - 1);

delete[]temp;
return true;
}

void Merge(int array[],int temp[],int left,int mid, int right)
{
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int idx = left;

//两个区域比较,如果第一个区域的数字小于第二个区域的数字,就让这个数字存到临时空间里
while (begin1 <= end1 && begin2 <= end2)
{
if (array[begin1] < array[begin2])
temp[idx++] = array[begin1++];
else
temp[idx++] = array[begin2++];
}

//如果一个区域读取完了 另一个没有读取完 则将后面的数字全部放到临时区域里
while (begin1 <= end1)
temp[idx++] = array[begin1++];
while (begin2 <= end2)
temp[idx++] = array[begin2++];
}

void MergeSort(int array[], int temp[], int left, int right)
{
if (left < right)
{
int mid = left + ((right - left) >> 1);
MergeSort(array, temp, left, mid);
MergeSort(array, temp, mid + 1, right);
Merge(array, temp, left, mid, right);
//将临时空间里的数字拷贝到数组里
memcpy(array + left, temp + left, sizeof(int)*(right - left + 1));
}
}


非递归实现:

void Merge(int array[],int temp[],int left,int mid, int right)
{
int begin1 = left;
int end1 = mid;
int begin2 = mid + 1;
int end2 = right;
int idx = left;

//两个区域比较,如果第一个区域的数字小于第二个区域的数字,就让这个数字存到临时空间里
while (begin1 <= end1 && begin2 <= end2)
{
if (array[begin1] < array[begin2])
temp[idx++] = array[begin1++];
else
temp[idx++] = array[begin2++];
}

//如果一个区域读取完了 另一个没有读取完 则将后面的数字全部放到临时区域里
while (begin1 <= end1)
temp[idx++] = array[begin1++];
while (begin2 <= end2)
temp[idx++] = array[begin2++];
}

bool Merge_Sort_Nor(int array[],int size)
{
int *temp = new int[size];
if (temp == NULL)
return false;

int gap = 1;
while (gap < size)
{
for (int idx = 0; idx < size; idx += 2 * gap)
{
int left = idx;
int mid = left + gap - 1;
int right = mid+gap;

if (mid >= size)
mid = size - 1;
if (right >= size)
right = size - 1;

Merge(array, temp, left, mid, right);
}
memcpy(array,temp,sizeof(int)*size);
gap <<= 1;
}
delete[]temp;
return true;
}


以上比较排序就全部完毕。

下面介绍两种非比较排序。

基数排序:

思路:以十进制为例,基数指的是数的位,如个位,十位百位等。先比较个位个位相同的放到一个中,再比较十位,最后比较百位。这样子最后读取出来,就是排序的结果。

有两种形式:

1.Leastsignificant digit(LSD)

短的关键字被认为是小的,排在前面,然后相同长度的关键字再按照词典顺序或者数字大小等进行排序。

2.Mostsignificance digit(MSD)

直接按照字典的顺序进行排序,对于字符串、单词或者是长度固定的整数排序比较合适。



基数排序流程:
将根据整数的最右边数字将其扔进相应的0~9号的篮子里,对于相同的数字要保持其原来的相对顺序(确保排序算法的稳定性),然后将篮子里的数如图所示的串起来,然后再进行第二趟的收集(按照第二位的数字进行收集),就这样不断的反复,当没有更多的位时,串起来的数字就是排好序的数字。
 
算法分析:
空间复杂度:
采用顺序分配,显然不合适。由于每个口袋都有可能存放所有的待排序的整数。所以,额外空间的需求为10n,太大了。采用链接分配是合理的。额外空间的需求为n,通常再增加指向每个口袋的首尾指针就可以了。在一般情况下,设每个键字的取值范围为d, 首尾指针共计2×radix个,总的空间为O(n+2×radix)。
 
时间复杂度:
上图示中每个数计有3位,因此执行3次分配和收集就可以了。在一般情况下,每个结点有d 位关键字,必须执行d 次分配和收集操作。
每次分配的代价:O(n)
每次收集的代价:O(radix)
总的代价为:O(d×(n+radix))

代码实现:

//基数排序
void RadixSortLSD(int array[], int arraySize)
{
int i, bucket[10], maxVal = 0, digitPosition = 1;
//找最大的数 看最大的数有几位
for (i = 0; i < arraySize; i++) {
if (array[i] > maxVal)
maxVal = array[i];
}

//最大值除以位数要大于0
while (maxVal / digitPosition > 0) {
int digitCount[10] = { 0 };

for (i = 0; i < arraySize; i++)
digitCount[array[i] / digitPosition % 10]++;

for (i = 1; i < 10; i++)
digitCount[i] += digitCount[i - 1];

for (i = arraySize - 1; i >= 0; i--)
bucket[--digitCount[array[i] / digitPosition % 10]] = array[i];

for (i = 0; i < arraySize; i++)
array[i] = bucket[i];

digitPosition *= 10;
}
}

桶排序:

思想:
桶排序的基本思想是将一个数据表分割成许多buckets,然后每个bucket各自排序,或用不同的排序算法,或者递归的使用bucket sort算法。也是典型的divide-and-conquer分而治之的策略。

步骤:

1.建立一堆buckets;

2.遍历原始数组,并将数据放入到各自的buckets当中;

3.对非空的buckets进行排序;

4.按照顺序遍历这些buckets并放回到原始数组中即可构成排序后的数组。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息