链表编程题

1.单链表的基本操作

#include<stdio.h>
#include<stdlib.h>
typedef int ElemType;


typedef struct LNode{
	ElemType data;
	struct LNode *next;
}LNode,*LinkList;

//头插法建立单链表
LinkList List_HeadInsert(LinkList &L)
{
	LNode *s;
	int x;
	L = (LinkList)malloc(sizeof(LNode));//创建头结点
	L->next = NULL;
	scanf("%d",&x);
	while(x!=9999)
	{
		s = (LNode *)malloc(sizeof(LNode));
		s->data = x;
		s->next = L->next;
		L->next = s;
		scanf("%d",&x);
	}
	return L;
}

//尾插法建立单链表
LinkList List_TailInsert(LinkList &L)
{
	L = (LinkList)malloc(sizeof(LNode)); //创建头结点
	LNode *s,*r=L;
	int x;
	scanf("%d",&x);
	while(x!=9999)
	{
		s = (LNode *)malloc(sizeof(LNode));
		s->data = x;
		r->next = s;
		r = s;
		scanf("%d",&x);
	}
	r->next = NULL;
	return L;
}

//按序号查找结点值
LNode *GetElem(LinkList L, int i)
{
	int j = 1;
	LNode *p = L->next;
	if(i==0)
		return L;
	if(i<1)
		return NULL;
	while(p&&j<i)
	{
		p=p->next;
		j++;
	}
	return p;
}
//按值查找结点
LNode *LocationElem(LinkList L,ElemType e)
{
	LNode *p = L->next;
	while(p&&p->data!=e)
	{
		p = p->next;
	}
	return p;
}

//获取链表长度
int getLength(LinkList L)
{
	int length = 0;
	LNode *p =L->next;
	while(p)
	{
		p = p->next;
		length++;
	}
	return length;
}

//打印链表
void printLinkList(LinkList L)
{
	LNode *p = L->next;
	while(p)
	{
		printf("%d ",p->data);
		p = p->next;
	}
	putchar('\n');
}

2.在带头结点的单链表L中,删除所有值为x的结点,并释放空间,假设值为x的结点不唯一,试编写算法以实现上述操作。

/*
算法思想:定义pre,p,r指针,其中pre始终为p的前驱结点,依次遍历链表,当要删除p结点时r用来记录p的后继防止断链,
	将p->data==x的结点删除即可,若p->data!=x,将pre和p依次后移
	时间复杂度为O(n),空间复杂度为O(1)
*/
void Del_X(LinkList &L,ElemType x)
{
	LNode *p,*pre,*r;
	pre = L; //pre指向头结点
	p = L->next; //p指向第一个元素
	while(p!=NULL)
	{
		if(p->data == x)
		{
			r = p->next;//记录p的后继结点,防止断链
			pre->next = p->next; //p的前驱指向p的后继结点
			free(p);//释放p结点空间
			p = r; //p指针后移
		}
		else
		{
			//若p结点值不等于x,将pre和p结点后移
			pre = p;
			p = p->next;
		}
	}
}

3.设L为带头结点的单链表,编写算法实现从尾到头反向输出每个结点的值。

/*
	算法思想:重新定义一个链表L2,将L依次用头插法插到L2中,遍历L2即可。
*/
void reverse_print(LinkList L)
{
	LinkList L2 = (LinkList)malloc(sizeof(LNode));//创建L2的头结点
	LNode *p = L->next,*q; //p用来遍历L链表,q临时存储p结点
	
	while(p)
	{
		q = (LNode *)malloc(sizeof(LNode));
		q->data = p->data;	 //将p的值域赋值给q,切记不能写成q = p
		q->next = L2->next;  //将q指向L2指向的元素
		L2->next = q;
		p = p->next;
	}
	printLinkList(L2);
}

4.试编写在带头结点的单链表L中删除一个最小值结点的高效算法(假设最小值结点是唯一的)。

/*
	算法思想:首先遍历第一遍列表,找出值最小的结点对应的值记为x,再定义两个指针p和prep
	当p->data=x时,将prep->next = p->next,并且释放p结点即可.
	时间复杂度为O(n),空间复杂度为O(1)
*/
void Del_Min(LinkList &L)
{
	LNode *p,*q,*pre = L; //q用来记录最小值处的结点,以便释放空间,pre为第二轮遍历时p的前驱
	p = L->next;
	int min = p->data;  //刚开始假设链表L的第一个元素为最小值min
	//遍历链表L,找出链表中的最小值min
	while(p)
	{
		if(p->data < min)
		{
			min = p->data;
		}
		p = p->next;
	}
	p = L->next; //找到min之后,重新将p指向链表的第一个元素
	while(p && p->data != min)
	{
		pre = p;
		p = p->next;
	}
	//遍历结束,找到x结点p,进行删除操作
	q = p;
	pre->next = p->next;
	free(q);
}

5.试编写算法将带头结点的单链表就地逆置,所谓"就地"是指辅助空间复杂度为O(1)。

/*
	算法思想:遍历链表L,依次将各个元素重新使用头插法插入L中,注意要防断链即可。
	时间复杂度为O(n),空间复杂度为O(1)
*/
void Reverse_LinkList(LinkList &L)
{
	LNode *p = L->next,*r;  //p指针指向链表第一个元素,r为p的后一个指针,防止将p插入后找不到后面的元素
	L->next = NULL;	//将L指向空
		
	while(p)
	{
		r = p->next;  //r指向p的下一个结点,防止插入p后后面元素发生断链
		//头插法插入元素
		p->next = L->next; 
		L->next = p;
		p =r;	//将p后移指向r的位置
	}	
}

6.有一个带头结点的单链表L,设计一个算法使其元素递增有序。

/*
	算法思想:用简单选择排序每次找到结点最大的值,将其用头插法插入链表,在将其删除直到把整个遍历删除干净为止

	时间复杂度为O(n²),空间复杂度为O(1)
	或者用空间换取时间,将链表存放进一维数组中,用排序时间复杂度为O(nlog2n)的算法对数组进行排序,排序完之后在依次插入
	链表中。此时的时间复杂度为O(log2n),空间复杂度为O(n)
*/
void sortList(LinkList &L)
{
	int length = getLength(L);//获取表长
	LinkList L2 = (LinkList)malloc(sizeof(LNode)); //重新定义一个表头
	LNode *p = L->next,*pre = L,*max = L->next,*maxpre = L;  //p为遍历指针,max是指向最大值的指针,pre和maxpre分别是p和max的前驱
	L2->next = NULL;
	while(length > 0)  //简单选择排序需要进行n轮(即表长多长进行多少轮)
	{
		while(p != NULL)
		{
			if(p->data > max->data)//遍历每一轮找出最大值元素
			{
				max = p;		//max用来标记当前结点是元素值最大的结点
				maxpre = pre;	//maxpre指向max的前序,此时即最大值的前序
			}
			pre = p;			//pre始终指向p的前驱
			p = p->next;		//p指针向后移动
		}
		//一轮结束后此时max结点即为最大值,将最大值用头插法插入进新表头(注意此时没有新创建空间)
		maxpre->next = max->next;//将max的前驱指向后继,()
		max->next = L2->next;	 //将最大元素按头插法指向L2的下个元素
		L2->next = max;			 //头结点指向max
		length--;				//将L的表长进行减一
		//重新将p,max,maxpre,pre初始化指向最初位置
		p = L->next;			
		max = L->next;
		maxpre = L;
		pre = L; 
	}
	L = L2;
}

7.设在一个带表头结点的单链表中所有元素结点的数据值无序,试编写一个函数,删除表中所有介于给定的两个值(作为函数参数给出)之间的元素的元素(若存在)。

/*
	算法思想:定义两个指针p和pre,p用来遍历链表,pre指向p的前驱,在遍历过程中,若此时结点p的值满足要删除条件
	将pre->next = p->next;free(p),但是要防止断链,可以新定义一个r指向p的next,若结点p的值不满足要删除条件
	直接将p和pre向后移动
*/
void Del_BetweenMN(LinkList &L, int m, int n)
{
	LNode *p = L->next,*pre = L,*r;
	
	while(p!=NULL)
	{
		//p的结点值满足删除条件
		if(p->data <= n && p->data >= m)
		{
			r = p->next; 			//删除前先将r指向p的下一个结点,防止发生断链
			pre->next = p->next;	//删除p结点
			free(p);       			//释放空间
			p = r;					//将p后移
		}
		else
		{
			pre = p;				//pre和p后移
			p = p->next;
		}
	}
}

8.给定两个单链表,编写算法找出两个链表的公共结点。

/*
	//注意公共结点和公共元素,区别14题。
	算法思想:思路1,暴力求解,外循环遍历L1,内循环遍历L2。依次查找各个元素,但是此时时间复杂度为O(lengthA*lengthB);
			  思路2,若表一样长,则用p和q指针同时遍历LA和LB,若在到达表尾之前有p=q,说明该结点即为公共结点
			  ,若表尾结点还不满足p=q则无公共结点。
			  若表不一样长,可以先让表长的移动k=|lengthA-lengthB|个元素,在同时移动q和p,判断在到达表尾之前是否有p=q即可,此时时间复杂度为O(lengthA+lengthB)
*/

LNode getCommon(LinkList LA,LinkList LB)
{
	int lengthA = getLength(LA);//获取LA长度
	int lengthB = getLength(LB);//获取LB长度
	int k; //将较长表和较短表的差值赋值给K
	LNode *p,*q;//p指针用来遍历较长链表,q指针用来遍历较短链表
	if(lengthA >= lengthB)
	{
		p= LA->next;
		q = LB->next; 
		k = lengthA -lengthB;
	}
	else
	{
		p= LB->next;
		q = LA->next; 
		k = lengthB -lengthA
	}
	//将较长表先移动k个位置
	while(k--)
		p = p->next;
	
	while(p)
	{
		if(p==q)
			return p;
		else
		{
			p = p->next;
			q = q->next;
		}
	}
	return NULL;
}

9.给定一个带头结点的单链表,设head为头指针,结点结构为(data,next),data为整型元素,next为指针,试写算法按递增次序输出单链表中各节点的数据元素,并释放结点所占的内存空间(要求:不允许使用数组作为辅助空间)。

/*
	算法思想:与06题一样,所以没写注释,对比着看只不过插入时一个用的头插法一个用的尾插法
	时间复杂度O(n²),空间复杂度为O(1)
*/
void sortList2(LinkList &L)
{
	LNode *p = L->next,*minp = L->next,*pre = L, *minpre = L;
	LinkList L2;
	L2 = (LinkList)malloc(sizeof(LNode));
	LNode *r =L2;
	int length = getLength(L);
	while(length > 0)
	{
		while(p!=NULL)
		{
			if(p->data < minp->data)
			{
				minp = p;
				minpre = pre;
			}
			else
			{
				pre = p;
				p = p->next;
			}
		}
		r->next = minp;
		r = minp;
		minpre->next = minp->next;
		minp->next = NULL;
		length--;
		p = L->next;
		minp =L->next;
		pre = L;
		minpre = L;
	}
	r->next = NULL;
	
	//输出元素并释放空间
	p = L2->next;
	while(p)
	{
		r = p->next;
		printf("%d ",p->data);
		free(p);
		p = r;
	}	
}

10.将一个带头结点的单链表A分解为两个带头结点的单链表A和B,使得A表中含有原表中序号为奇数的元素,而B表中含有原表中序号为偶数的元素,且保持其相对顺序不变。

/*
	算法思想: 用指针p遍历L,同时设置一个计数器count,初始为1,每遍历一个count++,用count来判断位于奇数序还是
	偶数序。将count%2=1的用尾插法插入LA中,建立一个LB,将count%2==0的用尾插法插入B中。
*/

LinkList divideList(LinkList &LA)
{
	int count = 1; //用来计数
	LinkList LB = (LinkList)malloc(sizeof(LNode)); //创建B链表的头结点
	LNode *rA = LA,*rB =LB, *p = LA->next; //p为遍历指针,rA为链表A的尾指针,rB为链表B的尾指针
	
	//遍历链表A
	while(p)
	{
		//序号为奇数的结点插入LA中
		if(count%2 == 1)
		{
			rA->next = p;
			rA = p;
		}
		//序号为偶数的结点插入LB中
		else
		{
			rB->next = p;
			rB = p;
		}
		count++;  //计数器每次加1
		p = p->next; //p后移
	}
	//遍历结束将尾指针的next置空
	rA->next = NULL;
	rB->next = NULL;
	return LB;
}

11.设C={a1,b1,a2,b2,…,an,bn}为线性表,采用带头结点的hc单链表存放,设计一个就地算法,将其拆分为两个线性表,使得A={a1,a2,a3,…,an},B={bn,bn-1,…,b1}。

/*算法思想: 与第10题思路一样,只不过链表B使用头插法*/
void divideList2(LinkList &LC)
{
	LinkList LA = (LinkList)malloc(sizeof(LNode));  	//创建A链表的头结点
	LinkList LB = (LinkList)malloc(sizeof(LNode));		//创建B链表的头结点
	int count = 1;		//创建计数器
	LNode *p = LC->next,*rA = LA,*r;			//p为C链表的遍历指针,rA为链表A的尾指针,r用来防止头插时断链
	
	while(p)
	{
		if(count%2 == 1)
		{
			rA->next = p;
			rA = p;
			p = p->next;
		}
		else
		{
			r = p->next;
			p->next = LB->next;
			LB->next = p;
			p = r;
		}
		count++;
		
	}
	rA->next = NULL;
	printLinkList(LA);
	printLinkList(LB);
}

12…在一个递增有序的线性表中,有数值相同的元素存在。若存储方式为单链表,设计算法去掉数值相同的元素,使表中不再有重复元素,例如(7,10,10,21,30,42,42,51,70)将变成(7,10,21,30,42,51,70)

/*
	算法思想:定义两个指针pre,p,pre指向p的前一个结点,遍历链表比较pre->data和p->data是否相等,如果相等,
	直接删除p结点,否则pre指向p,p后移
*/
void delSameElem(LinkList &L)
{
	LNode *pre = L->next,*p = pre->next;
	
	while(p)
	{
		if(pre->data == p->data)
		{
			pre->next = p->next;
			free(p);
			p = pre->next; 
		}
		else
		{
			pre = p;
			p = p->next;
		}
	}
	printLinkList(L);
}

13.假设有两个按元素值递增次序排列的线性表,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递减次序排列的单链表,并要求利用原来两个单链表的结点存放归并后的单链表。

/*
	算法思想:用p指针和q指针依次遍历LA和LB,将更小值的结点用头插法插入到LA中,直到遍历完一个链表,将另外一个链表剩余
	元素再依次插入到LA中即可。
void merge_decrease(LinkList &LA,LinkList &LB)
{
	LNode *p = LA->next, *q = LB->next,*r;//p指向LA的第一个元素,q指向LB的第一个元素,r用来防止头插时断链
	LA->next = NULL;
	while(p&&q)
	{
		if(p->data <= q->data)
		{
			r = p->next;
			p->next = LA->next;
			LA->next = p;
			p = r;
		}
		else
		{
			r = q->next;
			q->next = LA->next;
			LA->next = q;
			q = r;
		}
	}
	//如果LA还有剩余元素
	while(p)
	{
		r = p->next;
		p->next = LA->next;
		LA->next = p;
		p = r;
	}
	//如果LB还有剩余
	while(q)
	{
		r = q->next;
		q->next = LA->next;
		LA->next = q;
		q = r;
	}
}

*/

14.设A和B是两个单链表(带头结点),其中元素递增有序。设计一个算法从A和B中的公共元素产生链表C,要求不破坏A、B结点。

/*
	算法思想:定义p指针和q指针,分别用来遍历L1和L2链表,当p->data<q->data时,p后移
	q->data<p->data时,q后移,直到找到p->data = q->data时,说明此时结点即为公共结点,将其插入用尾插法插入到L3中,
	并将p和q同时后移,直到有一个表为空
*/

LinkList findCommon(LinkList L1,LinkList L2)
{
	LinkList L3 = (LinkList)malloc(sizeof(LNode));//创建新表头结点
	LNode *p = L1->next,*q = L2->next,*r = L3,*s; //p用来遍历L1,q用来遍历L2,r是L3的尾指针,用来用尾插法插入元素
	
	while(p&&q)
	{
		if(p->data < q->data)
			p = p->next;
		else if(q->data < p->data)
			q = p->next;
		else
		{
			//将p用尾插法插入L3中
			s = (LNode *)malloc(sizeof(LNode));
			s->data = p->data;
			r->next = s;
			r = s;
			//p和q后移
			p = p->next;
			q = q->next;
		}
	}
	r->next = NULL;  //最后将尾指针的next置空
	return L3;
}

15.已知两个链表A和B分别表示两个集合,其元素递增排列。编制函数,求A与B的交集,并存放于A链表中。

/*
	算法思想: 定义两个指针p和q分别遍历链表A和链表B,判断结点的元素值是否相等,如果不相等将小的值删除并后移
	如果相等,尾插法到LA上,直到遍历完整个遍历。
*/

void find_CommonElem(LinkList &LA, LinkList &LB)
{
	
	LNode *p = LA->next, *q = LB->next,*r = LA,*s; //r为尾指针,s用来防断链
	
	while(p&&q)
	{
		if(p->data < q->data)
		{
			s = p->next;
			free(s);
			p = s;
		}
		else if(p->data > q->data)
		{
			s = q->next;
			free(s);
			q = s;
		}
		//找到公共结点
		else
		{
			r->next = p;
			r = p;
			
			p = p->next;		//p后移
			s = q->next;		//记录q的下一个结点,以便删除一个相同的元素
			free(s);
			q = s;
		}
	}
	r->next = NULL;
	while(p)
	{
		s = p->next;
		free(s);
		p = s;
	}
	while(q)
	{
		s = q->next;
		free(s);
		q = s;
	}
}

16.两个整数序列A = a1,a2,a3,…,am和B = b1,b2,b3,…,bn已经存入两个单链表中,设计一个算法,判断序列B是否是序列A的连续子序列

/*
	算法思想: 暴力求解,p用来遍历链表A,q用来遍历链表LB,如果q和p不相等,
		q重头开始继续遍历,p从当前遍历结点的后继开始,
		如果q走到NULL,说明B是A的子序列,如果p走到NULL,说明B不是A的子序列
*/

int isNotChildStr(LinkList LA,LinkList LB)
{
	LNode *p = LA->next,*q = LB->next,*r = p; //r用来标记p每轮开始的下一位置
	
	if(getLength(LA) < getLength(LB))
		return 0;   //如果子串比主串还长返回错误
	
	while(p&&q)
	{
		if(q->data == p->data)
		{
			q = q->next;
			p = p->next;
		}
		else
		{
			
			r = r->next;
			p = r;
			q = LB->next; 
		}
	}
	//如果是因为q走到空说明B是A的子序列
	if(q == NULL)
		return 1;
	else			//说明p走到了空,B不是A的子序列
		return 0;
}

17.设计一个算法用来判断带头结点的循环双链表是否对称。

/*
	算法思想:定义两个指针p和q,p从头开始遍历,q从尾开始遍历,比较p和q的元素值是否相等,如果相等p=p->next,q = q->prior;
	直到p=q,遍历结束。如果在遍历期间有p->data!=q->data,直接返回0,说明不是对称链表
*/

int isNotSymmetry(DLinkList L)
{
	DNode *p = L->next, *q = L->prior;
	
	while(p != q)
	{
		if(p->data == q->data)
		{
			p = p->next;
			q = q->prior;
		}
		else
			return 0;
	}
	return 1;
}

18.有两个循环单链表,链表头指针分别为h1和h2,编写一个函数将链表h2连接到链表h1之后,要求链接后的链表仍保持循环链表形式。

/*
	算法思想:遍历两个链表,找到尾指针。将表LA的尾指针指向h2,表LB的尾指针指向h1
	
*/

void mergeList(LinkList &h1, LinkList &h2)
{
	LNode *p = h1, *q =h2; //分别是h1,h2的尾指针
	
	while(h1->next != h1)
		p = p->next;
	while(h2->next != h2)
		q= q->next;
	
	p->next = h2;
	q->next = h1;
}	

19.设有一个带头结点的循环单链表,其结点值均为正整数。设计一个算法,反复找出单链表中结点值最小的结点并输出。然后将该节点从中删除,直到单链表为空,再删除表头结点。

/*
	算法思想: 遍历n轮链表,每次找出值最小的元素,将其删掉
*/

void Del_Min2(LinkList &L)
{
	LNode *p = L->next, *pre = L, *min = L->next,*minpre = L;
	
	while(p->next != L)
	{
		while(p != L)
		{
			if(p->data < min->data)
			{
				min = p;
				minpre = pre;
			}
			pre = p;
			p = p->next;
		}
		printf("%d ",min->data);
		minpre->next = min->next;
		free(min);
		
		p = L->next;
		pre = L;
		min = L->next;
		minpre = L;
	}
	free(L);
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值