【数据结构】课程设计——幻方;求二叉树中叶子结点的个数;双向起泡排序
幻方
一、问题描述
幻方在我国古代称为“纵横图”。它是在一个 n×n 的矩阵中填入1到n2的数字(n为奇数),使得每一行、每一列、每条对角线的累加和都相等。例如图2-4就是一个3阶幻方。
二、基本要求
1、设计数据结构存储幻方;
2、设计算法完成任意n阶幻方的填数过程;
3、分析算法的时间复杂度。
三、概要设计
1、数据结构的设计
运用二维数组来存储幻方,幻方可以看作是行和列相等的二维数组,用i,j分别表示数组的行和列,可以通过控制下标操作实现对二维数组的赋值,存取,即完成对幻方的填数过程。
2、算法的设计
n为奇数:
函数内容:将1填入首行中间,再用“左上斜行法”将2至n^2填入
函数接口:void Square(int a[N][N],int n);
伪代码:
Ⅰ、由1开始填数,将1放在第0行的中间位置
Ⅱ、循环将2~n*n填入数组:
如果i-1≥0,不用调整,否则将其调整为n-1
如果j-1≥0,不用调整,否则将其调整为n-1
如果r[i][j] > 0,则在原位置的同一列下一行填入下一个数
n为双偶数:
函数内容:分为两部分,先将1至n^2按行列顺序填入,再将幻方等分成若干个4阶幻方,把各4阶幻方中对角线上的方格内数字与n阶幻方内以中心点为对称点的对角数字进行交换。
以八阶为例:交换白色线上的元素
函数接口:void dSquare(int a[N][N],int n);
伪代码:
Ⅰ、将1至n^2按行列顺序填入
Ⅱ、从每个四阶幻方左上到右下交换
Ⅲ、从每个四阶幻方左下到右上交换
n为单偶数(n=4*m+2,m=1,2,3……):
函数内容:需要调用奇数幻方函数实现。
(1)、分成A、B、C、D四个k阶方阵,这四个方阵都为奇方阵,
A B
C D
并利用奇数幻方方法将A、B、C、D填充。
(2)交换A、C元素,对其中间行,交换从中间列向右的m列各对应元素;对其他行,交换从左向右m列各对应元素。交换B、D元素,交换从中间列向左 (m –1) 列各对应元素。
如图以十阶为例,依次交换深蓝色、浅蓝色、绿色、黄色方框里对应位置的元素。
函数接口:void OuSquare(int a[N][N],int n);
伪代码:
Ⅰ、调用奇数幻方函数填充A
Ⅱ、利用A依次填充D、B、C:
a[i][j+k]=a[i][j]+2*k*k;a[i+k][j]=a[i][j]+3*k*k;
a[i+k][j+k]=a[i][j]+k*k;
Ⅲ、交换A、C元素中左上边长为(n-2)/4方形位置元素;
交换A、C元素中左下边长为(n-2)/4方形位置元素;
交换A、C元素从中间列向右的m列各对应元素;
交换B、D元素从中间列向左(m –1)列各对应元素。
3、抽象数据类型的设计
根据所设计的数据类型和函数接口,设计抽象数据类型:数组
四、详细设计
1、设计抽象数据类型对应的C类定义。
ADT Array
DataModel
数组是相同数据类型元素的集合;各元素的存储有先后顺序连续存放;数组元素用整个数组的名字和它在数组中的顺序位置表示,如a[0]表示名字为a的数组中的第一个元素。
Operation:
InitArray
输入:维度n和n组数据元素
功能:建立一个数组
输出:若维数n和各维长度合法,则构造相应的数组A,并返回OK
DestroyArray
输入:无
功能:销毁数组A
输出:释放数组的存储空间
Value
输入:&e,n个下标值
功能:将e赋值为指定的A的元素值
输出:若各下标不越界,则e赋值为所指定的A的元素值,并返回OK
Assign
输入:e,n个下标值
功能:将e的值赋给所指定的A的元素
输出:若各下标不越界,则将e的值赋给所指定的A的元素,并返回OK
endADT
2、设计每个成员函数
奇数幻方:
void Square(int a[N][N],int n)
{
if(n%2 != 1) return;
int i = 0; int j = n/2; //i和j 表示二维数组的行列下标
a[i][j] = 1; //将1填入第0行中间位置
for(int k = 2; k <= n*n; k++) //k表示即将填的数,将2~n * n填入数组
{
int iTemp = i, jTemp = j ; //暂存i和j的值
i = (i - 1 + n) % n ; //即i = i - 1; if (i < 0) i = n – 1;
j = (j - 1 + n) % n ; //即j = j - 1; if (j < 0) j = n – 1;
if (a[i][j] > 0)
{ //第i行第j列已经填数
i = (iTemp + 1) % n ;//即i = iTemp + 1;if (i == n) i = 0;
j = jTemp ; //原位置的同一列
}
a[i][j] = k; //在r[i][j]处填入k
}
}
双偶数幻方:
void dSquare(int a[N][N],int n)
{
if(n%4 != 0) return;
int i, j,x,y, temp, b = N % 4,c = 0;
int k = 1;
for(i = 0; i < N; i++) //将1~N的平方填入
{
for(j = 0; j < N; j++)
{
a[i][j] = k;
k = k + 1;
}
}
x=1;
for(i = 0; i < n; i++)//分成四阶,以n阶中心将各四阶对角线上数字中心对称
{
for(j = 0; j < n; j++)
{
if(i % 4 == 0 && (i-j) % 4 == 0) //从每个四阶幻方左上到右下
for(k = 0; k < 4; k++)
a[i+k][j+k] = n * n - a[i+k][j+k] + 1;
else
if(i % 4 == 3 && (i+j) % 4 == 3) //从左下到右上
for(k = 0; k < 4; k++)
a[i-k][j+k] = n*n - a[i-k][j+k] + 1;
}
}
}
单偶数幻方:
void Square(int a[N][N],int n)
{
if(n%2 != 1) return;
int i = 0; int j = n/2; //i和j 表示二维数组的行列下标
a[i][j] = 1; //将1填入第0行中间位置
for(int k = 2; k <= n*n; k++) //k表示即将填的数,将2~n * n填入数组
{
int iTemp = i, jTemp = j ; //暂存i和j的值
i = (i - 1 + n) % n ; //即i = i - 1; if (i < 0) i = n – 1;
j = (j - 1 + n) % n ; //即j = j - 1; if (j < 0) j = n – 1;
if (a[i][j] > 0)
{ //第i行第j列已经填数
i = (iTemp + 1) % n ; //即i = iTemp + 1; if (i == n) i = 0;
j = jTemp ; //原位置的同一列
}
a[i][j] = k; //在r[i][j]处填入k
}
}
void OuSquare(int a[N][N],int n)
{
int k,i,j,p,t;
k=n/2;
Square(a,k); //生成A
for(i=0; i<k; i++)
for(j=0; j<k; j++)
{
a[i][j+k]=a[i][j]+2*k*k; //生成D
a[i+k][j]=a[i][j]+3*k*k; //生成B
a[i+k][j+k]=a[i][j]+k*k; //生成C
}
t=(n-2)/4;
for(i=0; i<k; i++)
for(j=0; j<k; j++)
{
if((j<t)&&(i<t)) //交换A、C元素中左上边长为(n-2)/4方形位置元素
{
p=a[i][j];
a[i][j]=a[i+k][j];
a[i+k][j]=p;
}
if((j<t)&&(i>k-t-1)) //交换A、C元素中左下边长为(n-2)/4方形位置元素
{
p=a[i][j];
a[i][j]=a[i+k][j];
a[i+k][j]=p;
}
if((i>=t&&i<=k-t-1)&&(j>=t&&j<t*2))//交换A、C元素从中间列向右的m列各对应元素
{
p=a[i][j];
a[i][j]=a[i+k][j];
a[i+k][j]=p;
}
if(j>1&&j<=t) //交换B、D元素从中间列向左(m –1)列各对应元素
{
p=a[i][j+k];
a[i][j+k]=a[i+k][j+k];
a[i+k][j+k]=p;
}
}
}
3、设计主函数
int main()
{
int a[N][N];
for(int i = 0; i<N; i++)
{
for(int j = 0; j<N; j++)
a[i][j] = 0;
}
Square(a, N);
for(int i = 0; i<N; i++)
{
for(int j = 0; j<N; j++)
printf("%3d ",a[i][j]);
printf("\n");
}
return 0;
}
五、运行与测试
1、在调试程序的过程中遇到什么问题,是怎么解决的?
在调试程序的过程中确实遇到了问题,比如C语言的一些操作注意事项,格式等一些细节问题会有些忘记,但是这些基本操作经过几遍尝试回忆起来了,也锻炼了主函数的相关操作。
在程序的编写与测试过程中也会出现运行结果与预期不符的现象,经过不断增减语句测试语句作用,不断修改运行,最终达到预期效果。
2、设计了哪些测试数据?测试结果是什么?
在此课题中,涉及奇数幻方与单偶、双偶幻方,测试了不同符合及不符合条件的输入,符合条件的数可以正常输出正确幻方,不符合条件的输入不能输出正确幻方,输出全为0的幻方。
3、打印程序清单及运行结果。
奇数幻方:七阶运行结果
双偶数幻方:八阶运行结果
单双数幻方:十阶运行结果
六、总结与心得
本课题的算法中奇数幻方是将1到n^2的数依次填入;两种偶数幻方是将1到n^2的数按一定顺序填入后再做调整,函数的时间复杂度为O(n^2),这些算法的时间性能逐渐降低,但是数量级没有变化。有时候可调用其他函数使算法更加简单易读易操作,比如在单偶数幻方中调用奇数幻方实现。在填数过程中规则是交换,为了使问题更加容易解决,用运算代替交换,省去了不必要的判断,编写的程序需要不断测试改进以求更佳方案。
求二叉树中叶子结点的个数
一、问题描述
已知一棵二叉树,求该二叉树中叶子结点的个数。
二、基本要求
1、采用二叉链表作存储结构;
2、设计递归算法求叶子结点的个数;
3、设计非递归算法求叶子结点的个数。
三、概要设计
1、数据结构的设计
对二叉树,采用二叉树的数据结构,采用二叉链表作存储结构;
非递归算法中可采用栈的数据结构,用栈的后进先出特性遍历树的结点。
2、算法的设计
设计递归算法求叶子结点的个数:
本设计总体上划分为两个模块,需要完成的功能分别为建立二叉链表、求叶子节点个数。
建立二叉链表:
函数接口:BiNode *CreatBiTree()
伪代码:
Ⅰ、输入结点的数据信息ch
Ⅱ、当输入为“#”时,建立空树
否则:生成新的结点;赋值新结点的数据域为ch;
递归建立左子树、递归建立右子树。
求叶子节点个数(递归):
函数接口:int Count(BiNode*root)
伪代码:(设置全局变量count且初始化为0)
当root!=0时,Ⅰ、如果左右子树均为空,count++;
Ⅱ、Count(root->lchild);Count(root->rchild);
设计非递归算法求叶子结点的个数:
本设计总体上划分为两个模块,需要完成的功能分别为建立二叉链表、求叶子节点个数。
建立二叉链表:
函数接口:BiNode *CreatBiTree()
伪代码:
Ⅰ、输入结点的数据信息ch
Ⅱ、当输入为“#”时,建立空树
否则:生成新的结点;赋值新结点的数据域为ch;
递归建立左子树、递归建立右子树。
求叶子节点个数(非递归):
函数接口:int Count(BiTree root)
伪代码:
Ⅰ、申请一个栈空间,初始化count=0计数
Ⅱ、当前树的根节点不为空或栈不为空时,继续遍历
Ⅱ.Ⅰ.Ⅰ、当前树的根节点不为空时,count++;
Ⅱ.Ⅰ.Ⅱ、top++;s[top] = root;root = root->lchild;
Ⅱ.Ⅱ、栈不为空时,root = s[top];top--;
root = root ->rchild;
3、抽象数据类型的设计
根据所设计的数据类型和函数接口,设计抽象数据类型为二叉树、栈。
四、详细设计
4、设计抽象数据类型对应的C类定义。
二叉树的抽象数据类型定义:
ADT BiTree
DataModel
二叉树由一个根结点和两棵互不相交的左右子树构成,二叉树中的结点具有层次关系
Operation
InitBiTree
输入:无
功能:初始化一棵二叉树
输出:一个空的二叉树
CreaBiTree
输入:n个结点的数据信息
功能:建立一棵二叉树
输出:含有n个结点的二叉树
DestroyBiTree
输入:无
功能:销毁一棵二叉树
输出:释放二叉树占用的存储空间
PreOrder
输入:无
功能:前序遍历二叉树
输出:二叉树的前序遍历序列
InOrder
输入:无
功能:中序遍历二叉树
输出:二叉树的中序遍历序列
PostOrder
输入:无
功能:后序遍历二叉树
输出:二叉树的后序遍历序列
LevelOrder
输入:无
功能:层序遍历二叉树
输出:二叉树的层序遍历序列
endADT
栈的抽象数据类型定义:
ADT Stack
DataModel
栈中元素具有后进先出特性,相邻元素具有前驱和后继关系
Operation
InitStack
输入:无
功能:栈的初始化
输出:一个空栈
DestroyStack
输入:无
功能:栈的销毁
输出:释放栈的存储空间
Push
输入:元素值x
功能:入栈操作,在栈顶插入一个元素x
输出:如果插入成功,栈顶增加了一个元素,否则返回插入失败信息
Pop
输入:无
功能:出栈操作,删除栈顶元素
输出:如果删除成功,返回被删元素值,否则返回删除失败信息
GetTop
输入:无
功能:取栈顶元素,读取当前的栈顶元素
输出:若栈不空,返回当前的栈顶元素值,否则返回取站顶失败信息
Empty
输入:无
功能:判空操作,判断栈是否为空
输出:如果栈为空,返回1,否则返回0
endADT
5、设计每个成员函数
递归:
typedef char DataType;
typedef struct BiNode
{
DataType data;
struct BiNode *lchild,*rchild;
}BiNode;
BiNode *CreatBiTree()
{
char ch;
BiNode *root;
scanf("%c",&ch);
fflush(stdin);
if( ch == '#' )
root = NULL;
else
{
root = (BiNode*)malloc(sizeof(BiNode));
root -> data = ch;
root -> lchild = CreatBiTree();
root -> rchild = CreatBiTree();
}
return root;
}
int Count(BiNode*root)
{
if(root!=NULL)
{
if(root->lchild == NULL && root->rchild == NULL)
count++;
Count(root->lchild);
Count(root->rchild);
}
}
非递归:
typedef char DataType;
typedef struct BiNode
{
DataType data;
struct BiNode *lchild,*rchild;
}BiNode,*BiTree;
BiNode *CreatBiTree()
{
char ch;
BiNode *root;
scanf("%c",&ch);
fflush(stdin);
if( ch == '#' )
root = NULL;
else
{
root = (BiNode*)malloc(sizeof(BiNode));
root -> data = ch;
root -> lchild = CreatBiTree();
root -> rchild = CreatBiTree();
}
return root;
}
int Count(BiTree root)
{
int top = -1; // 栈为空
int count = 0;
BiTree s[MaxSize]; // 申请一个栈空间
while(root != NULL || top != -1)// 当前树的根节点不为空 或 栈不为空时 继续遍历
{
while(root !=NULL) // 当前树的根节点不为空
{
if(root->lchild == NULL && root ->rchild ==NULL)
count++; //左右子树都为空,是叶子结点
top++;
s[top] = root; //将当前的根节点入栈
root = root->lchild; //访问当前根节点的左子树
}
if(top!=-1) //栈不为空
{
root = s[top]; //让当前的根节点出栈
top--;
root = root ->rchild; //访问当前根节点的右子树
}
}
return count;
}
6、设计主函数
int main()
{
int a;
BiNode *root = NULL;
printf("请输入:\n");
root = CreatBiTree();
printf("该二叉树的根结点是:%c\n",root -> data);
printf("该二叉树的叶子结点个数是:");
a=Count(root);
printf("%d",a);
return 0;
}
五、运行与测试
1、在调试程序的过程中遇到什么问题,是怎么解决的?
在调试过程中有一些细节问题没有注意导致运行结果并不如意,此时我查找课本相关程序的编写方法,寻找思路,修改程序,并不断测试观察运行结果,输入不同数据来验证结果的正确性。
2、设计了哪些测试数据?测试结果是什么?
测试了多组数据,测试结果符合预期。下面列出其中几组:
输入:2#53### 根节点:2 叶子结点个数:1
输入:12#6#9### 根节点:1 叶子结点个数:1
输入:23#7##6## 根节点:2 叶子结点个数:2
输入:5#6#96### 根节点:5 叶子结点个数:1
3、打印程序清单及运行结果。
递归方法:
非递归:
六、总结与心得
本次的设计完成后,我体会到良好的编程习惯可以使我们在调试时减小工作量和提高效率,并且在调试过程中尝试各组不同数据的输入,从此更加快速的找到问题所在。另外,在程序设计的过程中要善于思考,判断需要用什么数据结构可以实现,尝试用不同数据结构实现问题。
双向起泡排序
一、问题描述
对一组数据进行双向起泡排序(假定按升序排列)。
二、基本要求
1、设计双向起泡排序算法;
2、将双向起泡排序算法的时间性能与起泡排序算法的时间性能进行比较
三、概要设计
1、数据结构的设计
在本实验中采用的数据结构为数组,由此完成对各个元素的比较交换实现排序。
2、算法的设计
本设计用flag标志标记交换位置,将数组中元素从前向后比较交换,实现将最大值排在最后;再从后向前比较交换,实现将最小值排在最前,此为一个来回。重复此过程直到没有交换元素即可。
函数接口:void BiBubble(int r[], int n)
伪代码:
Ⅰ、定义flag初始化为1
Ⅱ、当flag==1时,
flag = 0;
从前向后扫描,当r[j] > r[j + 1]时,flag = 1;交换r[j]与r[j+1];
从后向前扫描,当r[j-1] > r[j]时,flag = 1;交换r[j]与r[j-1];
i++;
3、抽象数据类型的设计
根据所设计的数据类型和函数接口,设计抽象数据类型:数组
四、详细设计
1、设计抽象数据类型对应的C++类定义。
ADT Array
DataModel
数组是相同数据类型元素的集合;各元素的存储有先后顺序连续存放;数组元素用整个数组的名字和它在数组中的顺序位置表示,如a[0]表示名字为a的数组中的第一个元素。
Operation:
InitArray
输入:维度n和n组数据元素
功能:建立一个数组
输出:若维数n和各维长度合法,则构造相应的数组A,并返回OK
DestroyArray
输入:无
功能:销毁数组A
输出:释放数组的存储空间
Value
输入:&e,n个下标值
功能:将e赋值为指定的A的元素值
输出:若各下标不越界,则e赋值为所指定的A的元素值,并返回OK
Assign
输入:e,n个下标值
功能:将e的值赋给所指定的A的元素
输出:若各下标不越界,则将e的值赋给所指定的A的元素,并返回OK
endADT
2、设计每个成员函数
void BiBubble(int r[], int n)
{
int flag = 1, i = 0,j,temp; //flag=1:有记录交换,flag=0:无记录交换
while (flag == 1)
{
flag = 0;
for (j = i; j < n - i - 1; j++) //从前向后扫描
if (r[j] > r[j + 1])
{
flag = 1;
temp = r[j];
r[j] = r[j+1];
r[j+1] = temp;
}
for (j = n -i - 1; j > i; j--) //从后向前
if (r[j - 1] > r[j])
{
flag = 1;
temp = r[j];
r[j] = r[j-1];
r[j-1] = temp;
}
i++;
}
}
3、设计主函数
int main()
{
int i;
int a[10]={1,3,6,9,5,2,0,7,4,8};
BiBubble(a,10);
for(i=0;i<10;i++)
printf("%3d",a[i]);
}
五、运行与测试
1、在调试程序的过程中遇到什么问题,是怎么解决的?
考虑到在本设计题目中需要判断循环边界情况,通过实际代数来检验算法的合理性。
2、设计了哪些测试数据?测试结果是什么?
数据:{1,3,6,9,5,2,0,7,4,8}
运行结果: 0 1 2 3 4 5 6 7 8 9
数据:{3,45,76,2,5,9,7,34,6,1}
运行结果: 1 2 3 5 6 7 9 34 45 76
3、打印程序清单及运行结果。
数据:{1,3,6,9,5,2,0,7,4,8}:
数据:{3,45,76,2,5,9,7,34,6,1}
六、总结与心得
双向起泡排序时间复杂度:O(n^2),空间复杂度:O(1)
比较:{2,3,4,5,1}双向起泡排序一个来回即可,而冒泡排序需要四趟。虽然两者时间复杂度的数量级相同,但是起泡排序在一定程度上时间复杂度好于冒泡排序,算法要不断思考改进的方法,不断测试改进。