单链表的增删改查

  • 一、单链表
  • 1.用代码定义一个单链表
  • 2.头插法建立单链表:
  • 2.1不带头结点的单链表
  • 2.2带头结点的单链表
  • 3.单链表的插入和删除
  • 3.1按位序插入(带头结点)
  • i = 1;//插入表头
  • i = 3;//插在表中
  • i = 5;//插在表尾
  • i = 6;//return false
  • 3.2按位序插入(不带头结点)
  • ①如果 i = 1(插在表头)
  • ②如果 i > 1…
  • 3.3指定结点的前插操作
  • ①传入头指针
  • ②不传入头指针
  • 3.4按位序删除(带头结点)
  • 3.5指定结点的删除(带头结点)
  • 4.单链表的查找
  • 4.1按位查找
  • 4.2按值查找
  • 5.建立单链表
  • 5.1尾插法建立单链表
  • 5.2头插法建立单链表
  • 6.小结
  • 二、双链表
  • 1.双链表的初始化(带头结点)
  • 2.双链表的插入(带头结点)
  • 3.双链表的删除(带头结点)
  • 4.双链表的遍历
  • 4.1后向遍历
  • 4.2前向遍历
  • 4.3前向遍历(跳过头结点)


一、单链表

定义:每个结点除了存放数据元素外,还要存储指向下一个节点的指针

android使用链表 链表的代码实现_结点

1.用代码定义一个单链表

typedef struct LNode{
	ElemType data;		//数据域
	struct LNode *next;	//指针域,指针指向下一个节点
}
struct LNode * p = (struct LNode *) malloc(sizeof(struct LNode));

typedef 关键字 —— 数据类型重命名
typedef <数据类型> <别名>
例子:typedef struct LNode LNode;
LNode * p = (LNode *) malloc(sizeof(LNode));

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

要表示一个单链表时,只需声明一个头指针 L ,指向单链表的第一个结点
LNode *L; //声明一个指向单链表第一个结点的指针
LinkList L; //声明一个指向单链表第一个结点的指针

typedef struct LNode{
	ElemType data;
	struct LNode *next;	
}LNode,*LinkList;
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 != NULL && j<i){
		p = p->next;
		j++;
	}
	return p;
}

强调这是一个单链表 ——使用 LinkList
强调这是一个结点 ——使用 LNode *

2.头插法建立单链表:

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 != 999){						//输入999表示结束
		s = (LNode *)malloc(sizeof(LNode));	//创建新结点
		s->data = x;
		s->next = L->next;
		L->next = s;					//将新结点插入表中,L为头指针
		scanf("%d",&x);
	}
	return L;
}

2.1不带头结点的单链表

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

//初始化一个空的单链表
bool InitList(LinkList &L){
	L = NULL;		//空表,暂时没有任何结点
	return true;
}

//判断单链表是否为空
bool Empty(LinkList L){
	if(L == NULL)
		return true;
	else
		return false;
}

void test(){
	LinkList L;
	InitList(L);
}

2.2带头结点的单链表

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

//初始化一个单链表(带头结点)
bool InitList(LinkList &L){
	L = (LNode* )malloc(sizeof(LNode));
	if(L == NULL)
		return false;		//内存不足,分配失败
	L->next = NULL;
	return true;
}

//判断单链表是否为空(带头结点)
bool Empty(LinkList L){
	if(L->next == NULL)
		return true;
	else
		return false;
}

void test(){
	LinkList L;
	InitList(L);
	//....后续代码
}

android使用链表 链表的代码实现_结点_02

3.单链表的插入和删除

android使用链表 链表的代码实现_结点_03


android使用链表 链表的代码实现_单链表_04

3.1按位序插入(带头结点)

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

//在第i个位置插入元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType e){
	if(i < 1)
		return false;
	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的第几个结点
	p = L;			//L指向头结点,头结点是第0个节点(不存数据)
	while(p != NULL && j < i-1){	//循环找到第i-1个结点
		p = p->next;
		j++;
	}
	if(p == NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode ));
	s->data = e;
	s->next = p->next;
	p->next = s;		//将结点s连接到p之后
	return true;		//插入成功
}
i = 1;//插入表头

android使用链表 链表的代码实现_结点_05

i = 3;//插在表中

android使用链表 链表的代码实现_数据结构_06

i = 5;//插在表尾

android使用链表 链表的代码实现_结点_07

i = 6;//return false

平均时间复杂度: O(n)

3.2按位序插入(不带头结点)

android使用链表 链表的代码实现_单链表_08

①如果 i = 1(插在表头)

android使用链表 链表的代码实现_结点_09

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

//在第i个位置插入元素e(不带头结点)
bool ListInsert(LinkList &L,int i,ElemType e){
	if(i < 1)
		return false;
	if( i = 1){
		LNode *s = (LNode *)malloc(sizeof(LNode ));
		s->data = e;
		s->next = L;
		L = s;		//头指针指向新结点
		return true;		//插入成功
	}
	
	LNode *p;		//指针p指向当前扫描到的结点
	int j = 1;		//当前p指向的第几个结点
	p = L;			//L指向头结点,头结点是第0个节点(不存数据)
	while(p != NULL && j < i-1){	//循环找到第i-1个结点
		p = p->next;
		j++;
	}
	if(p == NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode ));
	s->data = e;
	s->next = p->next;
	p->next = s;		//将结点s连接到p之后
	return true;		//插入成功
}
②如果 i > 1…

android使用链表 链表的代码实现_单链表_10

3.3指定结点的前插操作

①传入头指针

方法:循环查找p的前驱q,再对q后插
bool InSertPrioNode(LinkList L,LNode *p,ElemType e)

②不传入头指针

例:

android使用链表 链表的代码实现_android使用链表_11

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

bool InSertPrioNode(LinkList L,LNode *p,ElemType e){
	if(p == NULL)
		return false;
	LNode *s = (LNode *)malloc(sizeof(LNode ));
	if(s == NULL){
		return false;
	}
	s->next = p->next;
	p->next = s;			//新结点s连接到p之后
	s->data = p->data;		//将p中的元素复制到s中
	p->data = e;			//p中元素覆盖为e
	return true;
}

例:

android使用链表 链表的代码实现_链表_12

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

bool InSertPrioNode(LinkList L,LNode *p,ElemType *s){//此处传入的是一个指针
	if(p == NULL)
		return false;
	if(s == NULL){
		return false;
	}
	s->next = p->next;
	p->next = s;			//新结点s连接到p之后
	ElemType temp = p->data;
	p->data = s->data;		
	s->data = temp;			
	return true;
}

3.4按位序删除(带头结点)

android使用链表 链表的代码实现_结点_13

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

//删除在第i个位置的元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType &e){
	if(i < 1)
		return false;
	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的第几个结点
	p = L;			//L指向头结点,头结点是第0个节点(不存数据)
	while(p != NULL && j < i-1){	//循环找到第i-1个结点
		p = p->next;
		j++;
	}
	if(p == NULL)
		return false;
	if(p->next == NULL)		//在第i-1个结点之后已无其他结点
		return false;
	LNode *q = p->next;		//令q指向被删除结点
	e = q->data;			//用e返回元素的值
	p->next = q->next;		//将*q结点从链中“断开”
	free(q);		//释放结点的存储空间
	return true;		//删除成功
}

平均时间复杂度: O(n)

3.5指定结点的删除(带头结点)

方法1:传入头指针,循环寻找 p 的前驱结点
方法2:偷天换日(类似于结点前插的实现)

bool DeleteNode(LNode *p){
	if(p == NULL)
		return false;
	LNode *q = p->next;		//令q指向*p的后继结点
	p->data = p->next->data;	//和后继结点交换数据域
	p->next = q->next;		//将*q结点从链中“断开”
	free(q);		//释放结点的存储空间
	return true;		//删除成功
}

时间复杂度: O(1)

4.单链表的查找

4.1按位查找

//按位查找,找到返回第i个元素(带头结点)
LNode *GetElem(LinkList L,int i){
	if(i < 0)
		return NULL;
	LNode *p;		//指针p指向当前扫描到的结点
	int j = 0;		//当前p指向的第几个结点
	p = L;			//L指向头结点,头结点是第0个节点(不存数据)
	while(p != NULL && j < i){	//循环找到第i个结点
		p = p->next;
		j++;
	}
	return p;
}

平均时间复杂度: O(n)

4.2按值查找

LNode *LocateElem(LinkList L,ElemType e){
	LNode *p = L->next;
	whlie(p != NULL && p->data != e)
		p = p->next;
	return p;	
}

平均时间复杂度: O(n)

5.建立单链表

5.1尾插法建立单链表

LinkList List_TailInsert(LinkList &L){ //正向建立单链表
	int x; //设ElemType为整型
	L=(LinkList)malloc(sizeof(LNode)); //建立头结点
	LNode *s,*r=L; 			//r为表尾指针
	scanf("%d",&x); 		//输入结点的值
	while(x!=9999){ 		//输入9999表示结束
		s=(LNode *)malloc(sizeof(LNode));
		s->data=x;
		r->next=s;
		r=s; 				//r指向新的表尾结点
		scanf("%d",&x);
	}
	r->next=NULL;			 //尾结点指针置空
	return L;
}

5.2头插法建立单链表

LinkList List_HeadInsert(LinkList &L){ //逆向建立单链表
	LNode *s;
	int x;
	L=(LinkList)malloc(sizeof(LNode)); //创建头结点
	L->next=NULL; //初始为空链表
	scanf("%d",&x); //输入结点的值
	while(x!=9999){ //输入9999表示结束
		s=(LNode*)malloc(sizeof(LNode)); //创建新结点
		s->data=x;
		s->next=L->next;
		L->next=s; //将新结点插入表中, L为头指针
		scanf("%d",&x);
	}
	return L;
}

头插法、尾插法:核心就是初始化操作、 指定结点的后插操作

6.小结

单链表的局限性:无法逆向检索,有时候不太方便

二、双链表

android使用链表 链表的代码实现_结点_14

1.双链表的初始化(带头结点)

android使用链表 链表的代码实现_单链表_15

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,*DLikList;

//初始化双链表
bool InitDLinkList(DLikList &L){
	L=(DNode *)malloc(sizeof(DNode )); //创建头结点
	if(L == NULL)
		return false;
	L->prior = NULL;	//头结点的prior永远指向NULL
	L->next = NULL;		//头结点之后暂时还没有结点
	return true;
}

2.双链表的插入(带头结点)

android使用链表 链表的代码实现_结点_16


警告:如果p是最后一个结点,则需要做如下判断…

android使用链表 链表的代码实现_结点_17

bool InsertNextDNode(DNode *p,DNode *s){
	if(p==NULL || s==NULL)
		return false;
	s-next = p->next;
	if(p->next != NULL)
		p->next->prior = s;
	s->prior = p;
	p->next = s;
	return true;
}

3.双链表的删除(带头结点)

android使用链表 链表的代码实现_结点_18

//删除p结点的后继结点q
bool DeleteNextDNode(DNode *p){
	if(p == NULL)
		return false;
	DNode *q = p->next;
	if(q == NULL)
		return false;
	p->next = q->next;
	if(q->next != NULL)
		q->next->prior = p;
	free(q);
	return true;
}

void DestoryList(DLinkList &L){
//循环释放各个结点
	while(L->next != NULL)
		DeleteNextDNode(L);
	free(L);
	L = NULL;
}

4.双链表的遍历

4.1后向遍历

while (p!=NULL){
	//对结点p做相应处理,如打印
	p = p->next;
}

4.2前向遍历

while (p!=NULL){
	//对结点p做相应处理
	p = p->prior;
}

4.3前向遍历(跳过头结点)

while (p-> prior != NULL){
	//对结点p做相应处理
	p = p->prior;
}

双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。时间复杂度O(n)