实验四 实现桶式排序和基于桶式排序的基数排序
并用C语言设计堆栈,实现中缀表达式到后缀表达式的转换
一、实验描述
1.Radix Sort:实现桶式排序和基于桶式排序的基数排序。在基数B,数组长度n和最大元素值m中,对排序时间影响最大的是哪一个?元素在未排序数组中的顺序是否对时间复杂度有影响?设计试验证明你的想法。
2.Stack:用C语言设计堆栈,并实现中缀表达式到后缀表达式的转换。
二、实验分析
桶式排序的基本思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(O(n))。但桶排序并不是比较排序,他不受到O(nlogn)下限的影响。简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。
基于桶式排序的基数排序的基本思想:按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。
中缀表达式转后缀表达式:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或者优先级低于栈顶符号(乘除优先加减),则栈顶元素依次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。
三、实验设计
1.实现桶式排序
例如要对大小为[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个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任何排序法都可以。
最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这样就得到所有数字排好序的一个序列了。
假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果
对每个桶中的数字采用快速排序,那么整个算法的复杂度是
O(n+m*n/m*log(n/m))=O(n+nlogn-nlogm)
从上式看出,当m接近n的时候,桶排序复杂度接近O(n)
桶式排序的代码实现:
#include <iostream>
using namespace std;
void BucketSort(int *A, int Max, int Size){
int B[Max+1];
int i,j,count = 0;
memset(B, 0, (Max+1)*sizeof(int));
for (i = 0; i < Size; i++) {
j = A[i];
B[j] += 1;
}
for (i = 0; i <= Max; i++) {
if (B[i] > 0) {
for (j = 0; j < B[i]; j++) {
A[count] = i;
count++;
}
}
}
}
int main(int argc, const char * argv[])
{
int A[]={1, 2, 2, 7, 4, 9, 3, 5};
int Max = 9;
int Size = sizeof(A)/sizeof(int);
BucketSort(A, Max, Size);
for (int i = 0; i < Size; i++) {
printf("%d ",A[i]);
}
printf("\n");
return 0;
}
2.实现基于桶式排序的基数排序
假如我们有10个乱序的数字,第一趟排序之后:
0 | 1 | 512 | 343 | 64 | 125 | 216 | 27 | 8 | 729 |
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
表格的第二行是10个桶的列表,第一行是10个数字。
第一趟排序按照个位数对应排序,第二趟按照十位数,一个桶里能放下多个数。所以要二维数组。
基于桶式排序的基数排序的代码实现:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
using namespace std;
struct data
{
int key[2];
};
struct linklist
{
linklist *next;
data value;
linklist(data v,linklist *n):value(v),next(n) {}
~linklist()
{
if (next)
delete next;
}
};
void BucketSort(data *A,int N,int K,int y)
{
linklist *Bucket[101],*p;//建立桶
int i,j,k,M;
M=K/100+1;
memset(Bucket,0,sizeof(Bucket));
for (i=1; i<=N; i++)
{
k=A[i].key[y]/M; //把A中的每个元素按照的范围值放入对应桶中
Bucket[k]=new linklist(A[i],Bucket[k]);
}
for (k=j=0; k<=100; k++)
{
for (p=Bucket[k]; p; p=p->next)
j++;
for (p=Bucket[k],i=1; p; p=p->next,i++)
A[j-i+1]=p->value; //把桶中每个元素取出
delete Bucket[k];
}
}
void RadixSort(data *A,int N,int K)
{
for (int j=1; j>=0; j--) //从低优先到高优先 LSD
BucketSort(A,N,K,j);
}
int main()
{
int N=100,K=1000,i;
data *A=new data[N+1];
for (i=1; i<=N; i++)
{
A[i].key[0]=rand()%K+1;
A[i].key[1]=rand()%K+1;
}
RadixSort(A,N,K);
for (i=1; i<=N; i++)
printf("(%d,%d) ",A[i].key[0],A[i].key[1]);
printf("\n");
return 0;
}
3. 用C语言设计堆栈,并实现中缀表达式到后缀表达式的转换
(1)遇到操作数:直接输出(添加到后缀表达式中)
(2)栈为空时,遇到运算符,直接入栈
(3)遇到左括号:将其入栈
(4)遇到右括号:执行出栈操作,并将出栈的元素输出(添加到后缀表达式中),直到弹出栈的是左括号,左括号不输出(不添加到后缀表达式中)。
(5)遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素,然后将该运算符入栈
(6)最终将栈中的元素依次出栈,输出。
例如,中缀表达式a+b*c-d转换成后缀表达式之后为abc*+d-。
利用栈实现中缀表达式转后缀表达式的代码实现:
#include <iostream>
#include <string>
#include <stack>
#include <map>
using namespace std;
void InfixToPostfix(const string infix, string& postfix)
{
stack<char> mark; // 符号栈
std::map<char, int> priority; // 符号优先级
priority['+'] = 0;
priority['-'] = 0;
priority['*'] = 1;
priority['/'] = 1;
int infix_length = infix.size(); // 中缀表达式的字符长度
postfix.reserve(infix_length);
for(int i = 0; i < infix_length; ++i) {
switch(infix[i]) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
postfix.push_back(infix[i]);
break;
case '+':
case '-':
case '*':
case '/':
if(!mark.empty()) {
char markTop = mark.top();
while(markTop != '(' && priority[infix[i]] <= priority[markTop]) {
postfix.push_back(markTop);
mark.pop();
if(mark.empty()) {
break;
}
markTop = mark.top();
}
}
mark.push(infix[i]);
break;
case '(':
if(infix[i - 1] >= '0' && infix[i - 1] <= '9') { // 5(6/2-1) <==> 5*(6/2-1)
mark.push('*');
}
mark.push(infix[i]);
break;
case ')':
{
char markTop = mark.top();
while(markTop != '(') {
postfix.push_back(markTop);
mark.pop();
markTop = mark.top();
}
mark.pop();
}
break;
default:
break;
}
}
// 剩余的全部出栈
while(!mark.empty()) {
postfix.push_back(mark.top());
mark.pop();
}
}
int main(int argc, char const *argv[])
{
std::string infix = "1+2*3+(4*5+6)*7+(1+2)";
std::string postfix;
cout << "infix : " << infix << endl;
InfixToPostfix(infix, postfix);
cout << "postfix : " << postfix << endl;
return 0;
}
实例:
把1+2*3+(4*5+6)*7转换成123*+45*6+7*+:
infix(中缀表达式):1+2*3+(4*5+6)*7
postfix(后缀表达式):123*+45*6+7*+
四、实验总结
基数排序的性能比桶排序要略差。每一次关键字的桶分配都需要O(N)的时间复杂度,而且分配之后得到新的关键字序列又需要O(N)的时间复杂度。假如待排数据可以分为d个关键字,则基数排序的时间复杂度将是O(d*2N) ,当然d要远远小于N,因此基本上还是线性级别的。基数排序的空间复杂度为O(N+M),其中M为桶的数量。一般来说N>>M,因此额外空间需要大概N个左右。
但是,对比桶排序,基数排序每次需要的桶的数量并不多。而且基数排序几乎不需要任何“比较”操作,而桶排序在桶相对较少的情况下,桶内多个数据必须进行基于比较操作的排序。
通过“C语言设计堆栈,实现中缀表达式到后缀表达式的转换”的实验,我对栈的运用有了更进一步的认识。在转化的时候,将该字符与运算符栈顶的运算符的优先关系相比较。如果该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。否则将栈顶的运算符从栈中弹出,直到栈顶运算符的优先级低于当前运算符,将该字符入栈。计算过程则是:从左到右依次进栈遇字母入栈,遇见运算符将前面两个字母弹出,进行运算符运算后,将值再入栈,重复此过程,最终栈里只有一个元素,该元素的值就是计算结果。栈也被广泛应用于各种程序设计中,所以我们也要掌握它的应用,能够熟悉的运用它来解决一些实际问题。