数据结构-链表-刷题记录(part1)

  • 本部分因为前面有较多的复习内容,所以先发布前面一部分方便大家更清晰地查阅。

链表(因为是复习课,所以很多知识我会补加在注释里)

首先这一部分的题需要注重对指针本身的理解,否则做题就会模棱两可的,指针虽然可以改变指向,但是是并不能改变所指对象的值的。

这里引用一个指针方面的回答:

在C中内存,可以划分为三种,堆、栈和静态存储区。
栈就是auto变量存放区域,自动释放,在编译时候需要知道空间大小。
堆一般用作开辟动态存储区,需要人工开辟,人工释放,可以在运行是开辟。比如说c中的malloc和free函数,还有C++中的new和delete。
还有就是静态存储区,这个区域存放一些常量,不能改变值。

你说的
char *pp = "Welcomt to C";
*pp = "PP is changed";
这句话本身是错的,首先字符串不能那样赋值。pp指向一个字符,而不是整个一行。
就算换成
*pp = 'P'也是不对的,因为
char *pp = "Welcomt to C"; 
意思是在静态存储区放入"Welcomt to C",然后在栈内开辟一个指针pp,指向这个字符串。
也就是说pp在栈中,而其指向的字符串在静态存储区中。所以能改变pp指向,但是不能改变其指向的值。 

你想把C/C++学明白了,一定要让内存在你心中透明。

对于链表,还需要仔细一个理解的部分,是我们只需要做到将所有结点在逻辑上具有顺序即可,并不需要在物理顺序上相邻,这个地方在解题时一定要琢磨清楚.

*LinkList L,L是一个指针用->访问 L用.访问(就是一个结构体了)

再引用一个问题方便理解概念:

问题:int CreateLink_L(LinkList& L,int n){ //问题:为什么这里要加&

解答: 一般情况,向函数传递指针类型的参数,可以让函数改变指针指向的内容,并将改变的效果返回;这里要改变指针变量L本身的值,使它指向新开辟的内存空间L = (LinkList)malloc(sizeof(LNode)),而不是要改变L所指向的内容的值,所以,要么向函数传递L的引用,要么传递指向L的指针(指向指针的指针)

  1. 设计一个递归算法,删除不带头结点的单链表L中所有值为x的结点

    算法思路:既然要使用递归,那么就直接编写一个根据位置删除的算法,然后再在里面调用(L->next的删除算法即可),这样的时间复杂度也是需要全部查找,为O(n); L是首结点指针

typedef struct LNode{
    
    			//定义单链表结点类型
	ElemType data;
	struct LNode *next;
}LNode, *LinkList;
void Del_X(LinkList &L,ElemType x){
    
    
    //递归实现在单链表L中删除值为X的结点
    LNode *p;			//p指向待删除结点
    if(L==Null)			//递归出口
        return;			
    if(L->data==x){
    
    		//若L所指结点的值为x
        p=L;			//删除*L,并让L指向下一结点
        L=L->next;
        free(p);
        Del_x_3(L,x);	//递归调用
    }
    else				//若L所指结点的值不为x
        Del_x_3(L->next,x);	 //执行递归调用
}

值得注意的是,这里直接去掉p并不会造成断链,实际上因为L是引用,是直接对原链表进行操作,因此并不会出现断链的问题.(因为L是引用变量我们将L=L->next,也就等同于将原链表的上一个结点的指针域指向了下一个结点,所以并不会出现所谓断链的问题

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

    算法思路:

(错误解法) 解法一:这里我们仍然可以使用递归的方式来解题,只是需要注意的是,这次的L是头结点指针.,时间复杂度与上题相同为O(n);但需要注意,我这里并没有定义新的指针,直接使用了头指针进行扫描,这种代码习惯是不讲武德的,也不是很推荐大家使用这种方式,因为这样,头指针就丧失了。当然,大家也可以再新定义一个指针变量进行赋值操作。

void Del_X_2(LinkList &L,ElemType x){
    
    
	  //递归实现在单链表L中删除值为X的结点
    LNode *p;			//p指向待删除结点
    if(L==Null)			//递归出口
        return;
    L=L->next;
    if(L->data==x){
    
    		//若L所指结点的值为x
        p=L;			//删除*L,并让L指向下一结点
        L=L->next;
        free(p);
        Del_x_3(L,x);	//递归调用
    }
    else				//若L所指结点的值不为x
        Del_x_3(L->next,x);	 //执行递归调用
}

上面解法之所有有问题的原因是你会发现递归调用的方式依次会扫描两格,因为会执行两次L=L->next,所以当然是有问题的,所以最好还是采用解法2和解法3叭。或者找到一个方法先访问到首结点。
解法二:用p从头至尾扫描单链表,pre指向*p结点的前驱。若P所指结点的值为x,则删除,并让p指向下一个结点,否则让pre、p指针同步后移一个结点。

void Del_X_1(LinkList &L,ElemType x){
    
    
	//删除L中所有值为x的结点
	LNode *p=L->next,*pre=L, *q;//置p和pre的初始值
    //等于 LNode *p=L->next;
    	//   LNode *pre=L;
    		//	LNode *q;
	while(p!=Null){
    
    
		if(p->data==x){
    
    
			q=p;		//让q指向该结点
			p->next;  
			pre->next=p;	//删除*p结点
			free(q);		//释放其空间
	}
	else{
    
    					//否则,pre和p同步后移
		pre=q;	
		p=p->next;		
	}//else
}//while
}

本算法是在无序单链表中删除满足某种条件的所有结点,这里的条件是结点的值为x。实际上,这个条件是可以任意指定的,只要修改if条件即可。比如,我们要求删除值介于mink和maxk之间的所有结点,则只需将if语句修改为if(p->data->mink &&p->data<maxk).也就是说,这个算法较为万金油,改一改就可以应用于其他场景。

解法三:采用尾插法(采用尾插法的原因是要保持顺序)重新建立单链表。用p指针扫描L的所有结点,当其值不为x时将链接到L之后,否则将其释放。

void Del_x_2(Linklist &L,ElemType x){
    
    
	LNode *p=L->next,*r=L,*q; //r指向尾结点,其初值为头结点,保证下一个链表能够和原来的完全相同
	while(p!=Null){
    
    
		if(p->data!=x){
    
    	//将值不等于x的结点插到r的后面 
			r->next=p;	//将r指针指向的尾节点的指针域指向结点p 
			r=p;
			p=p->next; 		
		}
		else{
    
    			//等于就删除它 
			q=p;			/*这里这个缓冲指针是必要的,因为如果没有它,直接free(p),p就变成空指针了,也不能进行next下一步扫描操作)(free函数的作用是将指针指向的对象内存释放掉,指针变量依旧存在) */
			p=p->next	//继续执行扫描 
			free(q);	//释放空间 
		} 
	}//while
	r->next=Null;		//插入结束后置尾指针为Null 
}

上述两个算法都需要扫描一遍链表,时间复杂度为O(n),空间复杂度为O(1).

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

算法思想1:首先想到的肯定就是将链表逆置,可以通过一个新建一个辅助链表然后使用头插法插入原链表向后不断扫描的结点,这样顺序自然就反过来了,但值得注意的是,使用这种方法每次插入时还需要free原链表的空间,以防止浪费空间资源。

(重点)算法思想2:利用栈的思想,因为栈是先进后出的,所以刚好满足我们的需要,建立一个辅助栈,将链表元素逐个放入,再出栈即可。

(重点)算法思想3:使用递归的思想,每次访问一个结点时,再在里面递归输出它后面的一个结点,再输出它自己,这样访问整个链表,就达到了我们想要的目的。

void inverseOut(Linklist L){
    
    
	if(L->next!=Null){
    
    
	inverseOut(L->next);
}
	if(L!=null)
	print(L->data);
}

这里只需要不需要传入 LinkList &L的原因是我们不需要改变L指针变量本身的值,传入一个指向LinkList头部的指针即可,因为后面是通过不断调用L->next去访问的,而如果我们需要用L=L->next的方式去访问,那么就需要引用传参。(很重要,需要仔细理解)

猜你喜欢

转载自blog.csdn.net/weixin_45870904/article/details/111875839