链表操作的二级指针问题(C语言版)

本篇要解决的问题

        链表的所有相关操作中,什么操作用二级指针 ? 什么操作用一级指针?以及为什么这个操作要用一级指针?为什么那个操作要用二级指针?

背景

        我发现无论是观看学习视频,还是看数据结构相关的书籍,侧重点都是链表操作要如何移动相关结点。一些细小的地方,比如实现链表操作的伪代码函数的形参中,有的传一级指针,有的传二级指针。对于我等学渣。简直是无法理解。关键是有的同样的链表操作,有的视频用了一级指针,有的视频用了二级指针。很让人费解。而且网上与此相关的文章也只是给出了什么链表操作用什么指针,而没有给出为什么这样用? 本篇文章不仅让你知道链表的什么操作用一级指针,什么操作用二级指针,以及为什么要这么用。

结论

链表的所有操作中,头插法建表、尾插法建表、链表的初始化操作、链表的销毁操作我们需要使用二级指针。而链表的插入、删除、查询、判空、打印操作使用一级指针即可实现。

链表的插入、删除、查询、判空、打印操作使用一级指针可以实现,使用二级指针亦可以实现,但是没必要。后面会画图说明为什么二级指针也可以实现。

验证

基础代码:

#include <stdio.h>
#include <stdlib.h>

#define ElemType int
#define null NULL
//定义单链表链表节点
typedef struct LNode {
  ElemType data;
  struct LNode *next;
}LNode;

//打印链表 
void printList(LNode *list){
	if(list == null){
		printf("链表头结点都没了,你还想打印啥?\n");
		return;
	}
	LNode *s = list->next;
	while(s!=null){
		printf("%d\n",s->data);
		s=s->next;
	}
}

链表头插代码:T(n)=O(n)   需要使用二级指针

//头插法建表  
bool createListHead(LNode **list,ElemType content[],int len){
	*list = (LNode *)malloc(sizeof(LNode));
	(*list)->next = null;
	for(int i = 0;i<len;i++){    
		LNode *s =(LNode *) malloc(sizeof(LNode));
		s->data = content[i];
		s->next = (*list)->next;
		(*list)->next = s; 
	}
	return true;
}

假如链表头插使用一级指针,错误代码如下:

//头插法建表  
bool createListHead(LNode *list,ElemType content[],int len){
	list = (LNode *)malloc(sizeof(LNode));
	list->next = null;
	for(int i = 0;i<len;i++){    
		LNode *s =(LNode *) malloc(sizeof(LNode));
		s->data = content[i];
		s->next = list->next;
		list->next = s; 
	}
	return true;
}

链表尾插代码:  T(bn)=O(n)    需要使用二级指针

//尾插法建表  T(n) = O(n)
bool createListTail(LNode **list,ElemType content[],int len){
	*list =(LNode *)malloc(sizeof(LNode));
	LNode *r;
	r=*list;
	for(int i = 0;i<len;i++){
		LNode *s = (LNode *)malloc(sizeof(LNode));
		s->data = content[i];
		r->next = s; 
		r=r->next;
	}
	r->next=null;
	return true;
}

假如链表尾插使用一级指针,错误代码如下:

//尾插法建表  T(n) = O(n)
bool createListTail(LNode *list,ElemType content[],int len){
	list =(LNode *)malloc(sizeof(LNode));
	LNode *r;
	r=list;
	for(int i = 0;i<len;i++){
		LNode *s = (LNode *)malloc(sizeof(LNode));
		s->data = content[i];
		r->next = s; 
		r=r->next;
	}
	r->next=null;
	return true;
}

测试方法代码:

int main(int argc, char *argv[])
{
	int a[]={1,2,3,4,5,6,7,8,9,10};
	//数组长度 
	int len =sizeof(a)/sizeof(a[0]);
	LNode *head=null;
	createListHead(&head,a,len);
	//createListTail(&head,a,len);
	printList(head);
    return 0;
}

测试结果:

使用二级指针的测试结果:

链表操作的二级指针问题(C语言版)

使用一级指针的测试结果:链表操作的二级指针问题(C语言版)

分析原因:

此处以头插建立单链表代码进行分析,尾插法原因一样。

链表操作的二级指针问题(C语言版)

链表操作的二级指针问题(C语言版)

链表的初始化代码: T(n)=O(1)   需要使用二级指针

//单链表的初始化操作
bool initList(LNode **list){
	(*list) = (LNode *)malloc(sizeof(LNode));
	if((*list)==null){   //内存空间不足
		return false;
	}
	(*list)->next=null;
	return true;
}

假如链表初始化使用一级指针,错误代码如下:

//单链表的初始化操作 
bool initList(LNode *list){
	list = (LNode *)malloc(sizeof(LNode));
	if(list==null){   //内存空间不足
		return false;
	}
	list->next=null;
	return true;
}

测试方法代码:

int main(int argc, char *argv[])
{
	int a[]={1,2,3,4,5,6,7,8,9,10};
	//数组长度 
	int len =sizeof(a)/sizeof(a[0]);
	LNode *head=null;
    initList(&head);
    printList(head);
    return 0;
}

测试结果:

使用二级指针的测试结果:

链表操作的二级指针问题(C语言版)

使用一级指针的测试结果:

链表操作的二级指针问题(C语言版)

分析原因:

链表操作的二级指针问题(C语言版)

链表操作的二级指针问题(C语言版)

链表的销毁代码: T(n) = O(n)   需要使用二级指针

//链表的销毁代码
void destoryList(LNode **head){
	if((*head) == null){
		printf("该链表已经销毁,请勿重复销毁\n");
		return; 
	}
	while((*head)->next!=null){
		LNode *p = (*head)->next;
		(*head)->next = p->next;
		free(p);
	}
	free(*head);
	*head = null;
}

假如链表销毁使用一级指针,错误代码如下:

//链表的销毁代码
void destoryList(LNode *head){
	if(head == null){
		printf("该链表已经销毁,请勿重复销毁\n");
		return; 
	}
	while(head->next!=null){
		LNode *p = head->next;
		head->next = p->next;
		free(p);
	}
	free(head);
	head = null;
}

测试方法代码:

int main(int argc, char *argv[])
{
	int a[]={1,2,3,4,5,6,7,8,9,10};
	//数组长度 
	int len =sizeof(a)/sizeof(a[0]);
	LNode *head=null;
	createListHead(&head,a,len);
	printf("销毁链表前:\n");
	printList(head);
	destoryList(&head);
	printf("销毁链表后:\n");
	printList(head);
	return 0;
}

测试结果:

使用二级指针测试结果:

链表操作的二级指针问题(C语言版)

使用一级指针测试结果:

链表操作的二级指针问题(C语言版)

分析原因:

链表的销毁使用二级指针,不是因为一级指针不能把头结点和链表数据结点销毁掉,而是一级指针销毁掉数据结点和头结点之后,并不能对原来的头指针置为null,会导致原来的头指针成为野指针。

链表操作的二级指针问题(C语言版)

链表操作的二级指针问题(C语言版)

链表中能用一级指针实现的操作均能用二级指针实现,但是没必要。比如链表的判空操作。

链表判空代码: T(n)=O(1)     使用一级指针即可

//单链表的判空操作 T(n)=O(1)
bool isEmpty(LNode *list){          
	return list->next == null ? true:false; 
}

假如链表的判空代码使用二级指针,代码如下。 也能实现功能,但是没必要

//单链表的判空操作 T(n)=O(1)
bool isEmpty(LNode **list){          
	return (*list)->next == null ? true:false; 
}

测试方法代码:

int main(int argc, char *argv[])
{
	int a[]={1,2,3,4,5,6,7,8,9,10};
	//数组长度 
	int len =sizeof(a)/sizeof(a[0]);
	LNode *head=null;
	createListHead(&head,a,len); //头插法建立单链表
	//printList(head); 
 	printf("头插法建立单链表后,链表的状态%d\n",isEmpty(head));   // 0代表非空 
 	//对链表进行删除
	 deleteList(head);
	 printf("对链表进行删除之后,链表的状态%d\n",isEmpty(head)); //对链表进行删除之后,1代表空 
 	
	return 0;	
}

测试结果:

0==false,代表非空,1==true代表空。

使用二级指针的测试结果:

链表操作的二级指针问题(C语言版)

使用一级指针的测试结果:

链表操作的二级指针问题(C语言版)

分析原因:

 

链表操作的二级指针问题(C语言版)

链表操作的二级指针问题(C语言版)

至此,我们应该弄明白了,链表函数的形参什么时候用一级指针,什么时候用二级指针,以及为什么要用一级指针,为什么要用二级指针。因为不是专业学C\C++的,对C++的理解并不太深,如果有哪写的不对不严谨的地方,还望指出。

下面仅贴出链表剩余操作相关代码:

链表的查询操作(按位查询)   T(n)=O(n)   使用一级指针

//按位查询  T(n) = O(n)
LNode * GetElem(LNode *list,int i){
	if(list==null) return null;
	if(i<0) return false;
	LNode *p = list;
	int j = 0;
	while(p!=null && j<i){
		p=p->next;
		j++;
	}
	return p;
}

链表的查询操作(按值查询)   T(n)=O(n)   使用一级指针

//按值查询  T(n)=O(n)
LNode * LocateElem(LNode *list,ElemType e){
	if(list == null) return false;
	LNode *p = list->next;
	while(p!=null && p->data != e){
		p=p->next;
	}
	return p;
}

链表的删除操作(后删)    T(n)=O(1)    使用一级指针

//链表的后删操作      T(n)=O(1)
bool deleteElem2(LNode *p,ElemType *e){
	if(p==null) return false;
	*e = p->data;
	LNode *q = p->next;
	p->data = q->data;
	p->next = q->next;
	free(q);
	return true;
}

链表的删除操作(前删)   

方式一:找到要删除元素的前驱结点,修改前驱结点的指向即可。  T(n) =O(n)   使用一级指针

//链表前删操作  T(n)=O(n)
bool deleteElem1(LNode *list,LNode *p, ElemType *e){
	if(p==null) return false;
	while(list->next!=null && list->next!=p){
		list=list->next;
	}
	if(list->next==null){
		return false;
	}
	*e = p->data;
	list->next = p->next;
	free(p);
	return true;
}

方式二:将后一个元素的值赋值给当前元素,删除后一个元素即可 T(n) =O(1)  使用一级指针   删除最后一个元素不适用。 

//链表元素前删  T(n) = O(1)
bool deleteElem2(LNode *p,ElemType *e){
	if(p==null) return false;
	*e = p->data;
	LNode *q = p->next;
	p->data = q->data;
	p->next = q->next;
	free(q);
	return true;
}

链表的删除操作(保留头结点)   T(n) =O(n)   使用一级指针。

//链表的删除操作(保留头结点)   T(n)=O(n)
void deleteList(LNode *head){
	if(head->next == null){
		printf("该链表已经被删除了\n");
		return;
	}
	while(head->next!=null){
	LNode *p = head->next;
	head->next = p->next;
	free(p);
	}
}

链表的插入操作(后插)  T(n)=O(1)   使用一级指针

//链表的插入操作(后插)  T(n)=O(1)
bool insertAfterP(LNode *p,ElemType e){
	if(p==null) return false;
	LNode *s =(LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}

链表的插入操作(前插) 

方式一:循环找到前驱结点并插入 T(n) = O(n)  使用一级指针

//链表的前插 T(n) = O(n)
bool insertPriorP1(LNode *list,LNode *p,ElemType e){
	if(p==null) return false;  
	while(list->next!=null && list->next!= p){
		list=list->next;
	}
	if(list->next==null){
		return false;
	}
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->data = e;
	s->next = list->next;
	list->next = s;
	return true;
}

方式二:直接后插,并且交换两元素的值 T(n) = O(1)   使用一级指针

//链表的插入(前插)   T(n) = O(n)
bool insertPriorP2(LNode *p,ElemType e){
	if(p==null) return false;
	LNode *s = (LNode *)malloc(sizeof(LNode));
	s->next = p->next;
	p->next = s;
	s->data = p->data;
	p->data = e;
	return e;
}

 

 

 

 

 

 

上一篇:C++ 单链表(带头结点)


下一篇:数据结构学习——线性表