广州大学数据结构实验一

广州大学数据结构实验一,第1张

实验一 线性表、堆栈和队列的 *** 作与实现

开课实验室:计算机科学与工程实验(电子楼)       20201122

学院

计算机科学与网络工程学院

年级、专业、班

网络工程194

姓名

jwt

学号

实验课程

数据结构实验

成绩

实验项目

实验一 线性表、堆栈和队列的 *** 作与实现

指导老师

 

一、实验目的

1、线性表的链表实现:遍历、查找、插入、删除、翻转

2、栈的链式存储结构实现:入栈、出栈

3、队列的链式存储结构的实现:入队、出队

4、线性表、栈和队列的应用实现

二、使用仪器、器材

微机一台

*** 作系统:WinXP

编程软件:C++

三、实验内容及原理 1、线性表的链表实现

(1) 用随机函数生成10个3位整数(100~999),把这些整数存于链表中;

思路:首先调用initList(List &L)初始化链表,然后用后插法创建链表,数据域赋予随机数,随机数通过srand((unsigned)time(NULL))确定,并规定范围为rand()%900+100即100-999。

(2) 输出链表的内容;

思路:定义指针p指向首元结点,然后向后扫描,逐个数据域输出。

(3) 读入一个整数,查看该整数是否在表中,若在,输出其位置(首位置为1);思路:定义指针p指向首元结点,然后向后扫描,当找到与之相同的数据域时,返回下标。

(4) 读入一个整数,以及要插入的位置,把该整数插入到链表中,输出链表的内容(要求判断输入的位置是否合理);

思路:定义指针p指向首元结点,然后向后扫描,到达相应位置后,建立一个新结点,新结点指针域储存要插入结点的指针域,然后插入该结点后

(5) 读入一个整数,若该整数在链表里,删除该整数,输出链表的内容;

思路:先调用位置判断函数判断链表中是否有该数据,若有则找到该结点,创建一个新结点临时储存该结点,该结点的上一结点指向该结点的下一结点,释放要删除结点的空间。

(6) 把链表的内容翻转,输出链表的内容。

思路:因为用后插法创建了链表,所以可以用前插法再创建个新链表即可实现翻转。

2、栈的链式存储结构实现

(1) 用随机函数生成10个3位整数(100~999),把这些整数应用入栈 *** 作存于堆栈中,在入栈接口处设置断点①,按“F5”启动调试,按“F10”逐句执行,直到数据全部入栈。程序暂停时观察栈顶数据和栈顶位置;

思路:与创建单链表相似。

(2) 应用出栈 *** 作输出堆栈的内容,在出栈接口处设置断点②,按“F5”启动调试,按“F10”逐句执行,直到所有数据完全出栈,程序暂停时观察栈顶数据和栈顶位置的变化。

思路:与链表输出类似,栈顶元素要释放空间,并更新栈顶元素指向

3、队列的链式存储结构的实现

(1) 用随机函数生成10个3位整数(100~999),把这些整数应用入队 *** 作存于队列中;

思路:与创建链表类似,要更新队尾指针

(2) 应用遍历 *** 作输出队列的内容;

思路:与输出链表类似

(3) 把队列的内容翻转,应用出队 *** 作输出队列的内容。

思路:定义两个新结点临时储存队头空间,先将队头结点指向队尾,然后通过移动新结点把队尾倒数的元素逐个移到前边,实现翻转

4、线性表、栈和队列的应用实现

(1) 用随机函数生成10个3位整数(100~999),把这些整数存于单链表中,然后读入一个整数,以该值为基准把单链表分割为两部分,所有小于该值的结点排在大于或等于该值的结点之前。

思路:创建两个新链表,一个用于储存比该数小的数据,一个用于储存比该数大的数据,最后合并两个链表。

(2) 假设一个字符串中可以包含三种括号:( )[ ]{},且这三种括号可以按任意次序嵌套使用(如:“...[...{...}...[...]...]...(...)” 为合法嵌套,“...[...{... )...[...]...]...(...)”为不合法嵌套)。编写判别给定表达式中所含括号是否正确配对出现的算法,如果是合法嵌套则返回为true,如果是不符合法嵌套则返回为false。

思路:运用链栈可以解决,当读取到左括号时,把左括号压入栈,当读取到右括号时判断栈顶是否有相应的左括号与之匹配,若有,则栈顶元素出栈,循环下去即可解决。

(3) 用队列求解迷宫问题的最短路径

思路:

1.从起点开始,将其加入队列,设置距离为0;

2.从队列首端取出位置,将从这个位置能够到达的位置加入队列,并且让这些位置的距离为上一个位置的距离加上1;

3.一轮探索至无法继续向队列中添加元素,即当前所在点的四周情况全部考虑完成后,循环第二步,直到将终点添加到队列中。说明此时已经找到了路径;

4.求出并存储各条路径,并比较得出最短路径

四、源码及结果分析 一、线性表的链表实现 
//1.
void inputList(List &L)  //输入随机数,后插法创建链表
{
	List p, r;//p用于生成新结点,r为尾指针
	r = L;//尾指针初始化,指向头结点
	srand((unsigned)time(NULL));//随机数种子
	for (int i=0; i < 10; i++)
	{
		p = new LNode;//生成一个新结点p
		p->data = rand() % 900 + 100;//输入元素值赋给*p的数据域
		p->next = NULL;
		r->next = p;//将新结点插入尾结点
		r = p;//更新尾指针,指向新结点*p
	}
	cout << "创建成功" << endl;
}


//2.
void outputList(List L)  //输出链表元素
{
	List p;
	p = L->next;//p指向首元结点
	while (p!=NULL)//p向后扫描,直到p为空
	{
		cout << p->data << "  ";//输出p的数据域
		p = p->next;//p指向下一个节点
	}
	cout << endl;
}



//3.
int locateList(List L, int num) //判断链表中是否存在该元素,若有则返回相应下标
{
	List p=NULL;
	p = L->next;//p指向首元结点
	int temp = 1;// 首位置为1
	while (p != NULL)//p向后扫描,直到p为空
	{
		if (p->data == num)//查找成功
		  return temp;//返回相应元素的下标
		temp++;
		p = p->next;//p指向下一个节点
	}
	return -1;//找不到元素,返回-1
}

//4.
void insertList(List &L, int num,unsigned int index) //插入元素至链表指定位置,num为插入的元素,index为插入的位置
{
	List p = L;
	int j = 1;// 首位置为1
	while (p && (j < index))//p非空且初始位置小于要插入的指定位置时,向后扫描
	{
		p = p->next;
		j++;
	}
	if (!p && (j>index))//index大于链表长度或index<1
	{
		cout << "插入失败!" << endl;
		return;
	}
	else
	{
		List r = NULL;
		r = new LNode;//生成新结点
		r->data = num;//将*r的数据域置为num
		r->next = p->next;//新结点保存原来结点的下一指向
		p->next = r;//要插入位置的指针域指向新结点
		cout << "插入成功!" << endl;
	}
	cout << "插入新元素后的链表为: ";
	outputList(L);//输出插入后的链表
	if (index != locateList(L, num))//判断是否插入指定位置
		cout << "未插入指定位置!" << endl;
	else
		cout << "已插入指定位置!" << endl;
}

//5.
void deleteList(List &L, int num) //删除链表节点
{
	int i=locateList(L, num);//判断链表中是否有该元素,若有,则返回相应下标
	if (i == -1)
	{
		cout << "删除失败,链表中没有该元素!" << endl;
		return;
	}
	else
	{
		List p=L,q=NULL;
		int j = 1;
		while ((p->next) && (j < i))//查找第i-1个结点,p指向该结点
		{
			p = p->next;
			j++;
		}
		if ((!p->next) || (j>i))//当i>链表长度或者i<1时,删除位置不合理
		{
			cout << "删除位置不合理!" << endl;
			return;
		}
		else
		{
			q = new LNode;
			q = p->next;//q用于临时保存被删除结点的地址以备释放
			p->next = q->next;//改变删除节点前驱结点的指针域
			delete q;//释放删除结点的空间
			cout << "删除成功!" << endl;
		}
	}
}

//6.
void flipList(List L)//因为创建时用了后插法,所以用前插法创建新链表即可实现翻转
{
	List p = NULL,r=NULL,h=NULL;
	initList(p);//初始化新链表
	r = L->next;
	while (r)//原链表向后扫描
	{
		h = new LNode;//生成新结点
		h->data = r->data;//新结点的数据域置为原链表首元结点的数据
		r = r->next;//向后扫描
		h->next = p->next;
		p->next = h;//新结点插入头结点后
	}
	outputList(p);//输出翻转后的链表
}

创建链表,并赋予10个随机数,然后输出链表,输入666后,选择插入的位置为3,再输出链表,可以看到666已经插在555之后排在第三位;然后删除666,输出删除后的链表,结果正确;最后调用翻转链表函数,翻转成功!

二、栈的链式存储结构实现
//1.
void inputStack(LinkStack &S)//链栈的入栈
{
	LinkStack p=NULL;
	srand((unsigned)time(NULL));//随机数种子
	for (int i = 0; i < 10; i++)
	{
		p = new SNode;//生成新结点
		p->data = rand() % 900 + 100;//将数据域置为随机数100-999
		p->next = S;//将新结点插入栈顶
		S = p;//修改栈顶指针为p
	}
	cout << "入栈成功" << endl;
}

注:因为10个随机数调试太多,所以换成了2个随机数再进行调试

在入栈处设置断点1,先是955入栈,然后第二次循环到920入栈,栈顶元素变为了920

//2.
void popStack(LinkStack &S)//链栈的出栈
{
	while (S)
	{
		cout << S->data << " ";//输出元素
		LinkStack p = S;//p临时保存栈顶元素空间,以备释放
		S = S->next;//更新栈顶指针
		delete p;//释放原栈顶元素空间
	}
	cout << endl;
}

初始时栈顶元素为920与上边一致,说明入栈 *** 作正确,出栈时先释放920,下一步释放955,最后栈为空。

三、队列的链式存储结构的实现
//1.
void inputQueue(LinkQueue &Q) //创建队列
{
	QueuePtr p;
	srand((unsigned)time(NULL));
	for (int i = 0; i < 10; i++)
	{
		p = new QNode; //为入队元素分配结点空间,用指针p指向
		p->data = rand()%900+100; //赋予随机数
		p->next = NULL;
		Q.rear->next = p; //将新结点插入到队尾
		Q.rear = p; //修改队尾指针
	}
}
//2.
void outputQueue(LinkQueue &Q)//输出队列内容
{
	QueuePtr p;
	p = Q.front->next;//p指向队头元素
	while (p)//向后扫描
	{
		cout << p->data << "  ";
		p = p->next;
	}
	cout << endl;
}
//3.
void flipQueue(LinkQueue &Q)//翻转队列
{
	QueuePtr h = Q.front->next;//暂存队头空间
	QueuePtr r = Q.front->next;
	int count = countQueue(Q);//count用于储存队列长度,countQueue(Q)用于统计队列长度
	Q.front->next = Q.rear;//将队头元素置为队尾元素
	for (int i = 0; i < count; i++)
	{
		while (r)//r非空时向后扫描
		{
			if (r->next == Q.rear)//找到队尾指针(r为倒数第二个结点)
			{
				Q.rear->next = r;//队尾指针的指针域指向r
				Q.rear = r;//更新队尾指针
				break;
			}
			r = r->next;
		}
		r = h;//将队头空间赋予r,循环后达到翻转效果
	}
	r->next = NULL;//最后一个元素指针域置为空
	Q.rear = r;//更新队尾指针
	cout << "翻转后的队列为: ";
	outputQueue(Q);
}

先创建队列,并用outputQueue()输出原队列,然后调用翻转函数flipQueue(),翻转成功!

四、线性表、栈和队列的应用实现
//1.
List divideList(List L,int num)//分割链表
{
	List sh = NULL;//小链表头
	List st = NULL;//小链表尾
	List lh = NULL;//大链表头
	List lt = NULL;//大链表尾
	List p = L->next;//p指向原链表的首元结点
	while (p)//向后扫描
	{
		if (p->data < num)//若该结点的数据小于num
		{
			if (sh == NULL)//sh尚未初始化
			{
				initList(sh);
				sh->next = p;//把该结点插到sh之后
				st = sh->next;//更新尾指针st
			}
			else//若sh已经初始化,已有首元结点
			{
				st->next = p;//插入该结点
				st = st->next;//更新尾指针st
			}
		}
		else//若该结点的数据大于num
		{
			if (lh == NULL)//lh尚未初始化
			{
				initList(lh);
				lh->next = p;//把该结点插到lh之后
				lt = lh->next;//更新尾指针lt
			}
			else
			{
				lt->next = p;//插入该结点
				lt = lt->next;// 更新尾指针lt
			}
		}
		p = p->next;//指向下一结点
	}
	if (sh == NULL)//如果没有比num小的数,则小链表为空,返回大链表的头指针
	{
		return lh;
	}
	else
		st->next = lh->next;//将大链表的首元结点插入小链表的尾部
	return sh;//返回小链表的头指针
}

如图,首先创建链表赋予10个随机数,然后输入300,结果分割后的链表显示为,小于300的3个数都在左边,大于300的7个数都在右边

//2.
bool matching()//括号匹配
{
	LinkStack S;
	initStack(S);//初始化链栈
	bool judge = true;//判断匹配结果,初始化为真
	char str[20];//字符数组存储算术表达式
	cout << "请输入括号组成的字符串: ";
	cin >> str;
	int length = strlen(str);//获取输入算术表达式的长度
	for (int i = 0; i < length; i++)
	{
		switch (str[i])//根据符号的不同执行不同的操作
		{
		case '[':
		case '(':
		case '{':
			//读取到左括号时,将左括号压入栈
			pushStack(S, str[i]);
			break;
		case ']'://读取到右括号]
		{
					if (notEmptyStack(S) && getTop(S) == '[')//判断栈顶元素是否为对应的左括号
					{
						judge = true;
						popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
					}
					else
						judge = false;//不匹配,judge取假
					break;
		}
		case ')'://读取到右括号)
		{
					if (notEmptyStack(S) && getTop(S) == '(')//判断栈顶元素是否为对应的左括号
					{
						judge = true;
						popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
					}
					else
						judge = false;//不匹配,judge取假
					break;
		}
		case '}'://读取到右括号}
		{
					if (notEmptyStack(S) && getTop(S) == '{')//判断栈顶元素是否为对应的左括号
					{
						judge = true;
						popStack(S, str[i]);//若匹配,judge取真,栈顶元素出栈
					}
					else
						judge = false;//不匹配,judge取假
					break;
		}
		default:
			break;
		}
	}
	if (!notEmptyStack(S) && judge)//如果栈空并且judge判断为真,则匹配成功
		return true;
	else
		return false;
}

测试了两条表达式,第一条匹配失败,第二条匹配成功

//3.
typedef struct node
{
	int x, y;   //横纵坐标
	int pre;	//指向前驱点的坐标
}SqType;

typedef struct
{
	int x, y;
}item;

item assignMove(item moveEle[4])//向可移动的4个方向的移动后的坐标变化,从正右方顺时针开始
{
	for (int i = 0; i < 8; i++)
	{
		switch (i)
		{
		case 0:moveEle[0].x = 1; moveEle[0].y = 0; break;
		case 1:moveEle[1].x = 0; moveEle[1].y = 1; break;
		case 2:moveEle[2].x = -1; moveEle[2].y = 0; break;
		case 3:moveEle[3].x = 0; moveEle[3].y = -1; break;
		}
	}
	return moveEle[4];
}

void printPath(SqType *sq,int count[])
{
	int min = 999;//假设最短路径长度为999
	int i = 0;
	int arr[10];//用于储存各路径到达终点路径长度
	for (int k = 0; k < 10; k++)//初始化各路径路径长度为0
		arr[k] = 0;
	for (int j = 0; j < 10; j++)
	{
		if (count[j] != 0)//若有路径,则计算路径长度
		{
			i = count[j];
			while (i != -1)
			{
				i = sq[i].pre;
				arr[j]++;//更新路径长度
			}
		}
		if (count[j] == 0)//没有路径则跳出循环
			break;
	}
	int length = 0;
	for (int j = 0; j < 10; j++)//找出最短路径
	{
		if ((arr[j] < min) && (arr[j] != 0))
		{
			min = count[j];//令min等于最短路径的rear
			length = arr[j];
		}
	}
	while (min != -1)//打印路径
	{
		cout << sq[min].x << "," << sq[min].y << " " << endl;
		min = sq[min].pre;
	}
	cout << "最短路径长度为: " << length << endl;
}

void set(int count[], int n)//储存步数
{
	for (int i = 0; i < 10; i++)
	{
		if (count[i]== 0)
		{
			count[i] = n;
			break;
		}
	}
}

/*利用队列求最短路径*/
void path()
{  //*((int*)pia3 + i * 4 + j)
	item moveEle[4];
	moveEle[4] = assignMove(moveEle);
	//最外一圈建立“围墙”,防止数据溢出数组范围
	int maze[9][10] =
	{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
	  1, 0, 0, 0, 0, 0, 1, 0, 0, 1,
	  1, 0, 1, 1, 1, 0, 0, 1, 0, 1,
	  1, 0, 0, 1, 0, 1, 0, 0, 0, 1,
	  1, 1, 0, 1, 0, 1, 1, 0, 1, 1,
	  1, 0, 0, 1, 0, 0, 0, 0, 0, 1,
	  1, 0, 1, 1, 0, 1, 1, 0, 1, 1,
	  1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
	  1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
	int n = 9, m = 10;//行数和列数
	SqType sq[num];//用于记录所经过的坐标
	int front, rear;//前驱、后继下标
	int x, y, i, j, v;//i,j分别对应新的横纵坐标
	sq[0].x = 1;
	sq[0].y = 1;//设置起点
	sq[0].pre = -1;//已经过的点标记为-1
	front = rear = 0;
	maze[1][1] = -1;  //标记已经过的位置
	int count[10];//储存各路径的rear
	for (int k = 0; k < 10; k++)
		count[k] = 0;
	while (front <= rear)
	{
		//更新x,y的坐标
		x = sq[front].x;
		y = sq[front].y;
		for (v = 0; v<4; v++)//朝4各方向移动后的坐标变化
		{
			i = x + moveEle[v].x;
			j = y + moveEle[v].y;
			if (maze[j][i] == 0)//0表示通行,1表示没路走
			{
				rear++;//若可通行,步数+1
				//走下一步的坐标
				sq[rear].x = i;
				sq[rear].y = j;
				sq[rear].pre = front;//记录前驱
				maze[j][i] = -1;//已经过的点标记为-1
			}
			if ((i == m-2) && (j == n-2))//设置终点坐标
				set(count, rear);//储存步数
		}
		front++;
	}
	cout << "最短路径为: " << endl;
	printPath(sq, count);
}

因为输入迷宫太繁琐,所以直接定义了一个迷宫的二维数组。

通过执行path()来求解该迷宫的最短路径,并输出最短路径的行走经过以及最短路径的长度。

五、实验结果及分析

总结:1.遍历时要注意,创建的指针是指向头指针还是首元结点,不然可能会遍历不到最后一个元素提前结束,或者达不到想要的效果。

2.插入功能应该先要判断插入位置是否合理,否则可能输出乱码

3.链表合并时,应插入第二个链表的首元结点,如果插入的是头指针,会输出一堆数字。

4.括号匹配时,一定要认真看左右括号是否输入对,否则编译器不会提示你错误,要找半天。。。

5.某些程序出bug时,可以设置断点,逐条语句运行找到问题所在。

实验结果:实验结果基本都运行正确(即截屏所示)。

改进:在执行栈的 *** 作时,应该先写一个函数判断是否为空栈。队列链表类似。

Pass:仅作为实验参考,有些地方可能不够完善,见谅一下。转载请注明出处!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1498805.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-25
下一篇 2022-06-25

发表评论

登录后才能评论

评论列表(0条)

保存