DS 0821(第二章 单链表)

单链表

**单链表:**链式存储,每个结点处理存放数据元素外,还要存储指向下一个节点的指针

  1. 不要求大片的连续空间,改变容量方便
  2. 不可随机存取,要耗费一定空间存放指针

定义单链表的结点类型:

struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}; 

增加一个新的结点,在内存中申请一片空间,并用指针p指向这个结点

struct LNode *p=(struct LNode*)malloc(sizeof(struct LNode)); 

,之后可以使用一些代码逻辑,将p插入链表中

数据类型重命名
typedef<数据类型> <别名>

typedef struct LNode LNode;
即可以使用LNode代替struct LNode

typedef struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}LNode,*LinkList; 

等价于

struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}; 
typedef struct LNode LNode;
typedef struct LNode *LinkList;  //这是一个指向LNode的指针

//要表示一个单链表时,只需声明一个头指针L,指向单链表的第一个结点,再由next指针连接
 

声明一个指向单链表第一个结点的指针:

LNode *L;
LinkList L

两种方式
LNode*L 强调这是一个结点
LinkList 强调这是一个链表

如何初始化一个链表

不带头节点的单链表

typedef struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}LNode,*LinkList; 

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

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

void test(){
	LinkList L; //声明一个指向单链表的指针
	//初始化一个空表
	InitList(L);
	//....后续操作 
}

带头节点的单链表

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){
	return(L->next==NULL);
} 

void test(){
	LinkList L; //声明一个指向单链表的指针
	//初始化一个空表
	InitList(L);
	//....后续操作 
}

带头节点,写代码更方便

单链表的操作

插入删除

1. 带头节点的的按位序插入

LIstInsert(&L,i,e):插入操作,在表中第i个位置,插入指定元素e
找到第i-1个结点,修改其next值,头节点可以看作第0个位置

typedef struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}LNode,*LinkList;

//在第i个位置插入元素e(带头节点) 
bool ListInsert(LinkInsert &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;		   //与这一句不可以颠倒 
	return true;
} 
 

平均时间复杂度O(n)

2. 不带头节点的按位序插入(因为不带头节点所以i=1需要特殊处理)

typedef struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}LNode,*LinkList;

//在第i个位置插入元素e(带头节点) 
bool ListInsert(LinkInsert &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=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;		   //与这一句不可以颠倒 
	return true;
} 
 

不带头节点的话,则插入和删除第一个元素时,需要更改头指针L

3指定结点的后插操作

bool InsertNextNode(LNode *p,ElemType e){
	if(p==NULL)
	return false;
	LNode *s=(LNode *)malloc(sizeof(LNode));
	if(s==NULL)   return false;    //内存分配失败 
	s->data=e;
	s->next=p->next;
	p->next=s;
	return true;
} 

可以替换插入操作的后半部分:return InsertNextNode(p,e)

4.指定结点的前插操作
Q:如何找到p的前驱结点

  1. 传入头指针bool InsertPriorNode(LinkList L,LNode *p,elemtype e)
    循环查找p的前趋q,再对q后插 ,时间复杂度为O(n)
  2. 在p结点之后插入一个结点,将p的值复制到该节点中,并将e传入到p结点中 ,时间复杂度为O(1)
bool InsertPriorNode(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->data=p->data;
	p->data=e;
	return true;
} 

5.带头节点的按位序删除
ListDelete(&L,i,&e):删除操作。删除表中第i个位置的元素,并用e返回删除元素的值

找到第i-1个节点,将其指针指向i+1个结点,并释放第i个结点

typedef struct LNode{			//定义单链表结点类型 
	ElemType data;   	//每个结点存放一个数据元素,数据域
	struct LNode *next;	//指针指向下一个结点,指针域 
}LNode,*LinkList;

bool ListDelete(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)   //i值不合法
	return false;
	if(p->next==NULL) //第i-1个结点后已无其他结点 
	return false; 
	
	LNode *q=p->next; //让q指向被删除的结点
	e=q->data;       //用e返回被删除的值
	p->next=q->next;
	free(q);
	return true; 
}

最坏,平均时间复杂度O(n)

6.指定结点的删除
bool DeleteNode(LNode *p)
Q:删除结点p需要找到他的前趋结点的next指针

  1. 传入头指针,循环寻找p的前趋结点
  2. 偷天换日
bool DeleteNode(LNode *p){
	if(p==NULL)
	return false;
	
	LNode *q=p->next;  //让q指向p的后继结点
	p->data=p->next->data;  //p和后继结点q交换数据域 
	p->next=q->next;		// 将*q结点从连中断开 
	free(q);
	return true; 
}

如果p是最后一个结点,只能从表头开始依次寻找p的前驱,时间复杂度O(n)

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

单链表的查找操作

只讨论带头节点的情况
GetElem(L,i):按位查找操作,获取表中第i个位置的元素的值
LocateElem(L,e):按值查找操作。在表L中查找具有给定关键字值的元素。

注:在插入和删除中已经进行了查找的操作,找到i-1罢了

1. 按位查找

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)

2. 按值查找

LNode*LocateElem(LinkList L,ElemType e){
	LNode *p=L->next;
	//从第一个结点开始查找数据域为e的结点
	while(p!=NULL&&p->data!=e)
	p=p->next;
	return p; //找到后返回该结点的指针,否则返回NULL 
}

求表的长度

int Length(LinkList L){
	int len=0;
	LNode *p=L;
	while(p->next!=NULL){
		p=p->next;
		len++;
	}
	return len;
} 

单恋表的两种建立方法:头插法,尾插法

带头节点的单链表的情况
尾插法

  1. 初始化单链表
  2. 设置变量length记录链表长度
  3. while{
    每次取出一个元素e;
    ListInsert(L,length+1,e)
    length++;
    }
  4. 每次都从头开始遍历,时间复杂度为O(n²)

后插操作

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结点之后插入元素x
		r=s;							 //r指向新的表尾结点,永远保持r指向最后一个结点
		scanf("%d",&x); 
	} 
	r->next=NULL;
	return L;
}

头插法建立单链表

每一次取得一个新的数据元素,都插入到单链表的表头的位置

  • 即每次都对头节点进行一次后插操作
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;						//将新结点插入表中,L为头指针 
		scanf("%d",&x); 
	} 
		return L; 
}
  1. 好习惯:只要是初始化单链表,都先把头指针指向NULL
  2. 重要应用:链表的逆置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值