《数据结构》上机实验(第二章)——线性表Ⅰ

参考书目:《数据结构教程(第五版)》李春葆 主编
《数据结构》上机实验(第二章) Ⅱ
在这里插入图片描述

1.有一个带头结点的单链表 L = ( a 1 , b 1 , a 2 , b 2 , ⋯   , a n , b n ) L=(a_1,b_1,a_2,b_2,\cdots,a_n,b_n) L=(a1,b1,a2,b2,,an,bn),将其拆分成两个带头结点的单链表L1和L2,其中 L 1 = ( a 1 , a 2 , ⋯   , a n ) L1=(a_1,a_2,\cdots,a_n) L1=(a1,a2,,an) L 2 = ( b n , b n − 1 , ⋯   , b 1 ) L2=(b_n,b_{n-1},\cdots,b_1) L2=(bn,bn1,,b1)。要求L1使用L的头结点。

  • 算法思路:利用原单链表L中的所有结点通过改变指针域重组成两个单链表L1和L2。由于L1中结点的相对顺序与L中的相同,所以直接改变结点的指针域即可建立单链表L1;由于L2中结点的相对顺序与L中的相反,所以采用头插法建立单链表L2。
#include<stdio.h>
#include<malloc.h>
typedef int ElemType;
typedef struct LNode
{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

void InitList(LinkList &L) //初始化单链表
{
	L = (LinkList)malloc(sizeof(LNode));
	L->next = NULL;
}

void CreateList(LinkList &L) //尾插法创建单链表
{
	LNode *p = L,*s;
	for (int i = 0; i < 10; i++)
	{
		s = (LNode*)malloc(sizeof(LNode));
		s->data = i;
		s->next = p->next;
		p->next = s;
		p = p->next;
	}
}

void PrintList(LinkList &L) //输出函数
{
	LNode* p = L->next;
	while (p != NULL)
	{
		printf("%d ", p->data);
		p = p->next;
	}
}

void SplitList(LinkList& L, LinkList& L1, LinkList& L2) //拆分单链表L
{
	LNode* p = L->next, * q, * r1;
	L1 = L; //L1使用L的头结点
	r1 = L1; //r1指向L1
	L2 = (LinkList)malloc(sizeof(LNode*)); //创建L2的头结点
	L2->next = NULL; //置L2的指针域为NULL
	while (p != NULL)
	{
		r1->next = p; //采用尾插法将p插入L1中
		r1 = p;
		q = p->next;
		p = q->next;
		q->next = L2->next; //采用头插法将结点q插入L2中
		L2->next = q;
	}
	r1->next = NULL; //尾结点next置空
}

int main()
{
	LinkList L, L1, L2;
	InitList(L); //初始化单链表
	CreateList(L); //尾插法创建单链表
	printf("L中的值为:");
	PrintList(L); //调用函数,输出单链表L
	printf("\n");
	SplitList(L, L1, L2); //调用函数,拆分单链表L
	printf("L1中的值为:");
	PrintList(L1); //调用函数,输出单链表L1
	printf("\n");	
	printf("L2中的值为:");
	PrintList(L2); //调用函数,输出单链表L2
	printf("\n");
	return 0;
}

程序分析:

  • 由于L2采用尾插法,因此在第一次循环结束时,r1的指针域已经变为NULL。因此指针后移操作应该在r1的指针域变为NULL之前完成,当实现了后移操作后才能将r1的指针域与L2的指针域互换,否则后移操作会指向NULL。
    在这里插入图片描述
  • 运行结果:
    在这里插入图片描述
  • p刚开始指向L链表中的首结点,每一次while循环,p向后移动了两个结点的位置,当q为NULL时修改当前p指向的结点的指针域(置为NULL),此时再循环已经不满足条件了,循环结束。由于L1使用L的头结点,因此L1不需要开辟新的存储单元,而L2则需要开辟新的存储单元。
  • 时间复杂度: O ( n 2 ) O(\frac{n}{2}) (2n);空间复杂度: O ( n 2 ) O(\frac{n}{2}) O(2n)

2.删除一个单链表L中最大的结点(假设这样的结点唯一)。

  • 算法思路:在单链表中删除一个结点先要找到它的前驱结点,用指针p扫描整个单链表,pre指向结点p的前驱结点,在扫描时用max指向data域值最大的结点,mapre指向max所指结点的前驱结点。当单链表扫描完毕后,通过maxpre所指结点删除其后的结点,即删除了结点值最大的结点。
void CreateList(LinkList& L) //尾插法创建单链表
{
	LNode *p = L,*s;
	int num;
	printf("输入:\n");
	scanf("%d ", &num);
	while(num != 999)
	{
		s = (LNode*)malloc(sizeof(LNode));
		s->data = num;
		s->next = p->next;
		p->next = s;
		p = p->next;
		scanf("%d", &num);
	}
}

void MaxNum(LinkList &L)
{
	LNode* p = L->next, * pre = L, * max=p, * maxpre=L;
	ElemType maxnum=max->data;
	while (p != NULL)
	{
		if (p->data > maxnum)
		{
			max = p;
			maxnum = max->data;
			maxpre = pre;
		}
		pre = p;
		p = p->next;
	}
	maxpre->next = max->next;
	free(max);
}
CreateList(L);
MaxNum(L);

程序分析:

  • 通过scanf函数来给链表中各结点的数据域赋值,当输入999时,表示结束,不再开辟新的单元。
  • !要注意最大值在首结点的情况。
    这里是引用
  • 运行结果:
    在这里插入图片描述
  • 时间复杂度: O ( n ) O(n) (n);空间复杂度: O ( 1 ) O(1) O(1)

3.有一个带头结点的单链表L(至少有一个数据结点),使其递增有序排列。

  • 算法思路:由于单链表L中有一个以上的数据结点,首先构造一个只含头结点和首结点的有序单链表(只含一个数据结点的单链表一定是有序的)。然后扫描单链表L余下的结点(由p指向),有序单链表中通过比较找插入结点p的前驱结点(由pre指向它),在pre结点之后插入p结点。
void SortNumber(LinkList &L)
{
	LNode* p = L->next->next, * s, * pre;
	L->next->next = NULL;
	while (p != NULL)
	{	
		s = p->next;
		pre = L;
		while (pre->next != NULL && pre->next->data < p->data)
			pre = pre->next;
		p->next = pre->next;
		pre->next = p;
		p = s;
	}
}

程序分析:

在这里插入图片描述

  • 运行结果:
    这里是引用
  • 时间复杂度: O ( n 2 ) O(n^2) (n2);空间复杂度: O ( 1 ) O(1) O(1)

4.一个带头结点的双链表L,将其所有元素逆置。

算法思路:先构造只有一个头结点的空双链表L(利用原来的头结点),用p扫描双链表的所有数据结点,采用头插法将所指结点插入到L中。

void DiverseDList(DLinkList &L)
{
	DNode* p = L->next, * s;
	L->next = NULL;
	int i = 1;
	while (p!=NULL)
	{
		s = p->next;
		p->next = L->next;
		if (L->next != NULL)
			L->next->prior = p;
		p->prior = L;
		L->next = p;
		p = s;
	}
}

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度: O ( n ) O(n) (n);空间复杂度: O ( 1 ) O(1) O(1)

5.有一个带头结点的双链表L(至少有一个数据结点),使其元素递增有序排列。

  • 算法思路:参考第三题。

程序分析:

  • 运行结果:
    在这里插入图片描述
  • 时间复杂度: O ( n 2 ) O(n^2) (n2);空间复杂度: O ( 1 ) O(1) O(1)

6.有一个带头结点的循环单链表L,统计其data域值为x的结点个数。

  • 算法思路:扫描整个循环单链表,用count累计data域值为x的结点个数。
#include<stdio.h>
#include<malloc.h> //用malloc函数开辟新单元时需用此头文件
typedef int ElemType;
typedef struct LNode //定义单链表结点类型
{
	ElemType data; //数据域
	struct LNode* next; //指针域
}LNode, *LinkList;

void InitList(LNode * &L) //初始化线性表
{
	L = (LNode *)malloc(sizeof(LNode)); //创建头结点
	L->next = L; //其next域置为NULL
}

void CreateListR(LinkList &L) //头插法创建单链表
{
	LNode* s;
	int num;
	printf("输入:\n");
	scanf("%d", &num);
	while (num != 999)
	{
		s = (LNode*)malloc(sizeof(LNode)); //分配一个新的结点
		s->data = num;
		s->next = L->next;
		L->next = s;
		scanf("%d", &num);
	}
}

void DispList(LinkList &L) //输出单链表
{
	LNode* p = L->next; //p指向首结点
	while (p->next!=L)
	{
		printf("%d ", p->data); //输出p结点的data域
		p = p->next; //p移向下一个结点
	}
}

int CountNum(LinkList &L, ElemType num)
{
	LNode* p=L->next;
	int count = 0; //用来计数
	while (p != L) //当前结点不是尾结点
	{
		if (p->data == num) count++; //如果data域的值与num相等,计数值+1
		p = p->next; //指针后移
	}
	return count;
}

int main()
{
	LNode* L;
	InitList(L);
	CreateListR(L);
	printf("循环单链表中各元素的值为:");
	DispList(L);
	printf("\n");
	printf("单链表中与该数相等的数有:%d个",CountNum(L, 2));
}

程序分析:

  • 运行结果:
    在这里插入图片描述

7.有一个带头结点的循环双链表L,删除第一个data域值为x的结点。

  • 算法思路:指针扫描整个循环双链表来查找data值为x的结点,找到后删除p结点,并返回true,若未找到这样的结点返回false。
#include<iostream>
using namespace std;
#include<malloc.h> //用malloc函数开辟新单元时需用此头文件
typedef int ElemType;
typedef struct DNode //定义双链表结点类型
{
	ElemType data; //数据域
	struct DNode* prior; //指针域
	struct DNode* next; //指针域
}DNode, *DLinkList;

void InitList(DNode * &L) //初始化线性表
{
	L = (DNode *)malloc(sizeof(DNode)); //创建头结点
	L->prior = L; //其next域置为NULL
	L->next = L; //其next域置为NULL
}

int ListLength(DLinkList &L)
{
	DNode* p = L->next;
	int length = 0;
	while (p != L)
	{
		length++;
		p = p->next;
	}
	return length;
}

void CreateListF(DLinkList &L) //头插法创建双链表
{
	DNode* s;
	int num;
	printf("输入:\n");
	scanf("%d", &num);
	while (num != 999)
	{
		s = (DNode*)malloc(sizeof(DNode));
		s->data = num;
		s->next = L->next;
		s->next->prior = s;
		s->prior = L;
		L->next = s;
		scanf("%d", &num);
	}
}

void DispList(DLinkList &L) //输出双链表
{
	DNode* p = L->next; //p指向首结点
	while (p!=L)
	{
		printf("%d ", p->data); //输出p结点的data域
		p = p->next; //p移向下一个结点
	}
}

bool DeleteElem(DLinkList& L, ElemType num)
{
	DNode* p = L->next;
	while (p != L)
	{
		if (p->data == num)
		{
			p->prior->next = p->next;
			p->next->prior = p->prior;			
			free(p);
			return true;
		}
		p = p->next;
	}
	return false;
}

int main()
{
	DNode* L;
	InitList(L);
	CreateListF(L);

	printf("循环单链表中各元素的值为:");
	DispList(L);
	printf("\n");

	printf("当前循环双链表的长度为:%d",ListLength(L));
	printf("\n");

	cout << boolalpha << "删除成功了吗?——" << DeleteElem(L, 2) << endl;
	printf("当前循环双链表的长度为:%d", ListLength(L));
	printf("\n");
	printf("循环单链表中各元素的值为:");
	DispList(L);
	printf("\n");
}

程序分析:

  • 该算法利用循环双链表L的特点,通过L->prior直接找到尾结点,然后进行结点值的比较,从而判断L的数据结点是否对称。如果是非循环双链表,需要通过遍历查找尾结点,显然不如循环链表的性能好。
  • 运行结果:
    在这里插入图片描述

8. 判断带头结点的循环双链表L(含两个以上的结点)中的数据结点是否对称。

  • 算法思路:用p从左向右扫描L,q从右向左扫描L,然后循环。若p、q所指结点的data域不相等,则退出循环,返回false;否则继续比较,直到p==q(数据结点个数为奇数的情况)或者p.next==q(数据结点个数为偶数的情况)为止,这时返回true。
bool SymmElem(DLinkList& L)
{
	DNode* p = L->next, * q = L->prior;
	bool same = true;
	while (same)
	{
		if (p->data != q->data) same = false;
		else 
		{
			if (p == q || p->next == q) break;
			p = p->next;
			q = q->prior;
		}
	}
	return same;
}

程序分析:

  • 运行结果:
    在这里插入图片描述

9. 假设有两个表A和B,分别是 m 1 m_1 m1行、 n 1 n_1 n1列和 m 2 m_2 m2行、 n 2 n_2 n2列,它们简单自然连接结果C=A(自然链接i=j)B,其中i表示表A中列号,j表示表B中的列号,C为A和B的笛卡儿积中满足指定连接条件的所有记录组,该连接条件为表A的第i列与表B的第j列相等。

#include<stdio.h>
#include<malloc.h>
#define MaxCol 10 //最大列数
typedef int ElemType;
typedef struct Nodel //定义数据结点类型
{
	ElemType data[MaxCol]; //存放一行的数据
	struct Nodel* next; //指向后继数据节点
}DList; //行结点类型

typedef struct Node2
{
	int Row, Col; //行数和列数
	DList* next; //指向第一个数据结点
}HList; //头结点的类型

void CreateTable(HList* &h) //采用交互式方式建立单链表
{
	int i, j;
	DList * r, * s;
	h = (HList *)malloc(sizeof(HList)); //创建头结点
	h->next = NULL;
	printf("表的行数、列数:");
	scanf("%d%d", &h->Row, &h->Col); //输入表的行数和列数
	for (i = 0; i < h->Row; i++) //输入所以行的数据
	{
		printf("第%d行:", i + 1);
		s = (DList*)malloc(sizeof(DList));
		for (j = 0; j < h->Col; j++) //创建数据结点s
			scanf("%d", &s->data[j]); //输入一行的数据
		if (h->next == NULL) h->next = s; //插入第一个数据结点的情况
		else r->next = s; //插入其他数据结点的情况
		r = s; //r始终指向尾结点
	}
	r->next = NULL; //尾结点的next域置空
}

void DispTable(HList*& h)//输出单链表
{
	int j = 0;
	DList* p = h->next; //p指向开始行结点
	while (p != NULL) //扫描所有行
	{
		for (j = 0; j < h->Col; j++) //输出一行的数据
			printf("%4d", p->data[j]);
		printf("\n");
		p = p->next; //p指向下一个行节点
	}
}

void DestroyTable(HList*& h) //销毁单链表
{
	DList* pre = h->next, * p = pre->next;
	while (p != NULL)
	{
		free(pre);
		pre = p;
		p = p->next;
	}
	free(pre);
	free(h);
}

void LinkTable(HList *h1,HList* h2, HList*& h) //表连接
{
	int i, j, k;
	DList* p = h1->next, * q, * s, * r;
	printf("连接字段是:第1个表序号,第2个表序号:");
	scanf("%d%d", &i, &j);
	h = (HList*)malloc(sizeof(HList)); //创建结果表头结点
	h->Row = 0; //置行数为0
	h->Col = h1->Col + h2->Col; //置列数为表1和表2的列数和]
	h->next = NULL; //置next域为NULL
	while (p != NULL) //扫描表1
	{
		q = h2->next; //q指向表2的首结点
		while (q != NULL) //扫描表2
		{
			if (p->data[i - 1] == q->data[j - 1]) //对应字段值相等
			{
				s = (DList*)malloc(sizeof(DList)); //创建一个数据结点s
				for (k = 0; k < h1->Col; k++) //复制表1的当前行
					s->data[k] = p->data[k];
				for (k = 0; k < h2->Col; k++)
					s->data[h1->Col + k] = q->data[k]; //复制表2的当前行
				if (h->next == NULL) //若插入的是第一个数据结点
					h->next = s; //将s结点插入到头结点之后将s结点插入到结点r之后
				else
					r->next = s; //若插人其他数据结点
				r = s; //r始终指向尾结点
				h->Row++; //表行数增1
			}
			q = q->next; //表2后移一个结点
		}
		p = p->next; //表1后移一个结点
	}
	r->next = NULL; //表尾结点的next域置空				
}


int main()
{
	HList* h1, * h2, * h; 
	printf("表1:\n");
	CreateTable(h1); //创建表1
	printf("表2:\n");
	CreateTable(h2); //创建表2
	LinkTable(h1, h2, h); //连接两个表
	printf("连接结果表:\n");
	DispTable(h); //输出连接结果
	DestroyTable(h1); //销毁单链表h1
	DestroyTable(h2); //销毁单链表h2
	DestroyTable(h); //销毁单链表h
	return 0;
}

程序分析:

  • 建立单链表的时间复杂度为 O ( m × n ) O(m×n) O(m×n),其中m为表的行数、n为表的列数。
  • 销毁单链表的时间复杂度为 O ( m ) O(m) O(m),其中m为表的行数。
  • 输出单链表的时间复杂度为 O ( m × n ) O(m×n) O(m×n),其中m为表的行数、n为表的列数。
    在这里插入图片描述
  • 运行结果:
    在这里插入图片描述

10. 已知3个带头结点的单链表LA、LB和LC中的结点均依元素值递增排列(假设每个单链表不存在数据值相同的结点,但3个单链表中可能存在数据值相同的结点),设计一个算法对LA链表进行如下操作:使操作后的链表LA中仅留下3个表中均包含的数据元素的结点,且没有数据值相同的结点,并释放LA中所有的无用结点。

  • 算法思想:先以单链表LA的头结点作为一个空表,r指向这个新建单链表的尾结点。以pa扫描单链表LA的数据结点,判断它是否在单链表LB和LC中,若同时在LB和LC中,表示pa所指结点是公共元素,则将其链接到r所指结点之后,否则删除之。
void CommNode(LinkList& LA, LinkList& LB, LinkList& LC)
{
	LNode* pa = LA->next, * pb = LB->next, * pc = LC->next, * r = LA, * p ; 
	LA->next = NULL; //此时LA作为新建单链表的头结点
	while (pa != NULL)
	{
		while (pb != NULL && pa->data > pb->data) //pa结点与LB中的pb结点进行比较
			pb = pb->next;
		while (pc != NULL && pa->data > pc->data) //pa结点与LC中的pc结点进行比较
			pc = pc->next;
		if (pb != NULL && pc != NULL &&
			pa->data == pb->data && pa->data == pc->data) //若pa是公共结点
		{
			r->next = pa; //将pa结点插入到LA中
			r = pa;
			pa = pa->next; //pa移向下一个结点
		}
		else //若pa结点不是公共结点,则删除
		{
			p = pa;
			pa = pa->next; //pa移向下一个结点
			free(p); //释放非公共结点
		}
	}
	r->next = NULL;
}

程序分析:

  • 在上述算法中,指向LA、LB、LC单链表的指针pa、pb、pc都没有出现回溯过程,即每个单链表均只扫描一遍,所以算法的时间复杂度为O(m+n+p)空间复杂度O(1)。其中m、n、p分别是3个表的长度。在这里插入图片描述
  • 运行结果:
    在这里插入图片描述
  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值