链表操作总结-包括链表合并、插入删除、清空复制、逆序

本文详细介绍了链表的各种操作,包括有序链表的合并,无需额外空间的合并策略,链表清空操作,链表逆置,以及链表的插入和删除操作。此外,还探讨了头结点在链表中的作用和不同情况。对于合并,文章提供了针对不同合并规则的算法实现,如合并并删除重复元素和保留共同元素。

A. 关于有序表合并操作
线性结构合并操作可以分为两类,一类是顺序表(顺序存储),另一类是链表(链式存储),二者在有序表合并时的操作并不完全相同。详细分析如下:
①顺序表:由于顺序表属于顺序存储结构,当需要插入或删除表中元素时需要移动该位置后续的各元素,所以按通常方法,应开辟一个临时的存储表,用于存放合并后的数据。如不开辟一个临时的顺序表用于存储合并后的内容,则需在每次插入时移动后续元素,时间复杂度更大。
基本操作:
i. 开辟临时存储空间,数组长度为A表长度+B表长度;
ii. 取A,B中第一个元素a0,b0进行比较:如a0小于b0,将a0放入临时数组,a0变为a1继续比较;如a0小于等于b0,将b0放入临时数组,b0变为b1继续比较。重复操作。
iii. 当A或B表到达数组尾部时结束循环。
iv. 当A未到数组尾部,则将剩余部分放入临时数组;当B未到数组尾部,则将剩余部分放入临时数组。上述二个操作最后只会执行其一。
v. 将临时数组的值通过循环赋值给A表并返回。
具体的算法略。

②单链表单链表的优点在于插入和删除结点时无需移动元素,因此不用开辟额外的临时存储空间用于存放最终结果,可直接修改其中一个链表以得到结果并返回。
基本操作:
i. 声明指针变量pa指向A的头结点,声明pb指向B的首元结点(这里默认链表有头结点Lnode)。
ii. 比较pa->next->data和pb->data,如pa->next->data>pb->data。那么将pb结点放在pa之后,并删除pb所指结点;如pa->next->data<pb->data,pa = pa->next。
iii. 当pa->next或pb为NULL时结束循环。
iv. 如pb不为空,则把pb剩余部分链接到pa之后,返回。
具体算法见下:
void Merger(Lnode * L, Lnode * T){
 Lnode *p = L;
 Lnode *psave;
 Lnode *q = T->next;
 Lnode *qsave;
 while (p->next && q){
  if (p->next->data < q->data){
   p = p->next;
  }
  else{
   qsave = q->next;
   q->next = p->next;
   p->next = q;
   q = qsave;
   p = p->next;
  }
 }
 if (q) p->next = q;
 return;
}

③不同的合并规则:有时合并规则会有变化,总结后基本有两类。
i. 合并A,B并删除A,B中都存在的元素
基本思想:对于ai, bj不相等的情况操作同上;当ai == bj时则删除ai, bj,ai->ai+1,bj->bj+1
ii. 合并有序链表A,B且仅保留A,B中都存在的元素
基本思想:对于ai, bj不相等的情况,删除二者中较小者;当ai == bj时则删除bj,ai->ai+1。
④合并要求:
应注意合并要求,比如不允许使用malloc申请存储空间, 不含相同元素。不同要求需要对算法进行一定程度上的修改。
⑤多表合并:
多表合并时类似于两表合并操作,只是每次需要判断三表中ai,bj,ck中最小者进行相应的操作。
这里放上一段算法供参考(将A,B,C表合并为A,仅保留A,B,C中都包含的元素,不允许malloc申请空间,同时释放无用的结点):
Lnode * ChooseSame3(Lnode * L, Lnode * &T, Lnode * &Q){
	if (!L || !L->next || !T || !T->next || !Q || !Q->next) return NULL;
	Lnode * pl = L;
	Lnode * pt = T;
	Lnode * pq = Q;
	Lnode * plsave = NULL;
	Lnode * ptsave = NULL;
	Lnode * pqsave = NULL;
	while (pl->next && pt->next && pq->next){
		if (pl->next->data == pt->next->data && pl->next->data == pq->next->data && pl->next->next->data != pl->next->data){
			pl = pl->next;
			ptsave = pt->next;
			pt->next = ptsave->next;
			free(ptsave);
			ptsave = NULL;
			pqsave = pq->next;
			pq->next = pqsave->next;
			free(pqsave);
			pqsave = NULL;
		}
		else if (pl->next->data == pt->next->data && pl->next->data == pq->next->data){
			plsave = pl->next;
			pl->next = plsave->next;
			free(plsave);
			plsave = NULL;
		}
		else{
			DeleteMinData(pl, pt, pq);
		}
	}
	while (pl->next){
		plsave = pl->next;
		pl->next = plsave->next;
		free(plsave);
	}
	while (pt->next){
		ptsave = pt->next;
		pt->next = ptsave->next;
		free(ptsave);
	}
	while (pq->next){
		pqsave = pq->next;
		pq->next = pqsave->next;
		free(pqsave);
	}
	free(T);
	free(Q);
	T = Q = NULL;
	return L;
}

void DeleteMinData(Lnode * &pl, Lnode * &pt, Lnode * &pq){
	if (pl->next->data <= pt->next->data && pl->next->data <= pq->next->data){
		Lnode * plsave = pl->next;
		pl->next = plsave->next;
		free(plsave);
	}
	else if (pt->next->data <= pl->next->data && pt->next->data <= pq->next->data){
		Lnode * ptsave = pt->next;
		pt->next = ptsave->next;
		free(ptsave);
	}
	else if (pq->next->data <= ql->next->data && pq->next->data <= qt->next->data){
		Lnode *pqsave = pq->next;
		pq->next = pqsave->next;
		free(pqsave);
	}
}
B. 关于链表清空操作
链表清空操作主要就是采用头插法类似的思想。
含有头结点的链表:
Lnode *p = L->next;
while (p){
    L->next = p->next;
    free(p);
    p = L->next;
}
free(L);
L = NULL;//释放头结点


关于逆置操作
逆置操作无需用malloc申请空间,直接从L->next->next处断开,采用头插法逆置。
Lnode * p = L->next->next;
L->next->next = NULL;
Lnode *psave;
while (p){
    psave = p->next;
    p->next = L->next;
    L->next = p;
    p = psave;
}


C. 关于插入和删除操作
如在p的下一节点插入或删除,则按常用方法;
如删除p或在p的位置插入节点,则:
i. 删除:把p的下一节点的数据域拷贝到p节点,然后删除下一节点;
ii. 插入:在p后插入一个节点,把p节点数据域拷贝到后一节点中,再把新的数据赋给p节点。
上述两种方法只是在结果上等同于插入或删除本节点,但实际上也是插入或删除下一节点。
如在p的上一节点出插入或删除。则必须从头搜索或用双向链表。

D. 关于头结点的利弊
这里讨论三种情况。
i. 不含头结点,只有头指针。
这种情况,头指针直接指向第一个结点,最为简单,但在某些操作时需要区别对待,如尾插法插入节点时,需要判断head是否为NULL。
ii. 含头结点,且头结点与普通链表节点相同。
这种情况,头结点仅作一个空节点,带来的便利就是统一操作,尾插法或判断空表时,无需区别对待。
iii. 含头结点,且与普通链表节点不同。
这种头结点一般用于存储链表信息,如链表长度等。但在某些操作时需区别对待,同第一种情况。

E. 补充:
传参时*p和*&p的区别:
前者可以修改p所指节点的内容(包括指针域和数据域,即p->next和p->data都可修改)。
后者除了能够修改p所指节点内容外还能修改p所指的节点地址,因此当采用*&p时,从被调函数返回调用函数时,p所指结点是被调函数中p最终所指向的结点,可能被修改。如不希望修改可采用Lnode * const &p。
由于&为引用传参,可以免去一些时空开销,设计算法时应斟酌考虑。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值