线性表(逻辑结构)--链表(存储结构)

单链表

每个节点除去存放数据元素外,还需存储指向下一个节点的指针

定义

struct LNode{
	ElementType data;
	struct LNode *next;
};

//增加一个新的节点,在内存中申请新的节点所需要的空间,并用指针P指向这个节点
struct Lnode *p = (struct LNode *)malloc(sizeof(struct LNode));
//用typedef实现结构体的重命名,方便声明结构体类型的节点
typedef struct LNode LNode;
typedef struct Lnode *LinkList;
//后续声明只需要用LNode来代替struct LNode
LNode *p = (LNode *)malloc(sizeof(LNode));	
//上面两步也可以合并来完成
typedef struct LNode{
	EleType data;
	struct LNode *next;
}LNode,*LinkList;
//其中LNode是结构体类型,LinkList是指针类型
Lnode *p = (LNode *)malloc(sizeof(LNode));
LinkList q = (LinkList)malloc(sizeof(Lndoe));

LNode和LinkList本质上只是结构体类型和结构体的指针类型,但在使用时可以用LNode表示节点,用LinkList表示单链表更容易逻辑清晰。

例如在实现获取链表中第i个元素的函数中

//返回类型为LNode *突出返回的是一个节点
//入参类型LintList强调传入的是单链表
LNode *GetElementInLinkList(LinkList L,int i){
	if(i < 1)
		return NULL;
	else if(i = 1)
		return L;
	else
	{
		LNode *p = L;
		for(int j = 0;j < i&&p!=NULL;j++){
			p = p -> next;
		}
		return p;
	}
}

实现

不带头节点

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

//初始化一个空的链表
bool InitList(LinkList &L){
	L = NULL;
	return true;
}

带头节点

带头结点可以用相同的代码逻辑处理第一个数据点和后续的数据点

typedef struct LNode{
	int data;
	struct LNode *next;
}Lnode, *LinkList;

//初始化一个单链表(带头节点)
bool InitList(LinkList &L){
	L = (LNode)malloc(sizeof(LNode));
	if(L == NULL)
		return false;//malloc分配内存空间失败
	L -> next = NULL;
	return ture; 
}

基本操作的实现

插入

按位序插入
  • 带头节点
  • 不带头节点

在第i个位置插入指定的元素e

//带头节点
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
bool ListInsert(LinkList &L,int i,int e){
	LNode *p;
	p = L;//p指向L指向的地址
	if(i < 1)
		return false;
	for(int j = 0;j < i - 1 && L != NULL;j++){
		p = p -> next;
	}
	if(p == NULL)
		return false;
	LNode *q =(LNode)malloc(sizeof(Lnode));
	q -> data = e;
	q -> next = p -> next;
	p -> next = q;
	return true;
}

上述代码没有改变L的指向,传入指针的地址看起来是危险且复杂的,做如下修改

typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
bool ListInsert(LinkList p,int i,int e){
	if(i < 1)
		return false;
	for(int j = 0;j < i - 1 && L != NULL;j++){
		p = p -> next;
	} 
	if(p == NULL)
		return false;
	LNode *q =(LNode)malloc(sizeof(Lnode));
	q -> data = e;
	q -> next = p -> next;
	p -> next = q;
	return true;
}

这样做减少了用到的变量,且不用进行结构体指针的地址运算。
时间复杂度非常容易得出
最小O(1)
最大O(n)
平均O(n)
当不带头节点时,NodeList的指向是可能发生变化的,所以需要传入指针的地址改变其指向

//不带头节点
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
bool ListInsert(LinkList &L,int i,int e){
	if(i == 1){
		LNode *h = (LNode *)malloc(sizeof(LNode));
		if(h == NULL)
			return false;
		h -> data = e;
		h -> next = L;
		&L = &h;
		return true;
	}
	LNode *p;
	p = L;//p指向L指向的地址
	if(i < 1)
		return false;
	for(int j = 0;j < i - 1 && L != NULL;j++){
		p = p -> next;
	}
	if(p == NULL)
		return false;
	LNode *q =(LNode *)malloc(sizeof(Lnode));
	if(q ==NULL)
		return false;
	q -> data = e;
	q -> next = p -> next;
	p -> next = q;
	return true;
}
指定节点的后插操作
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
//在p节点后插入
bool InsertNextNode(LNode *p,int e){
	if(p == NULL)
		return false;
	LNode *s = (LNode *)(malloc(sizeof(LNode));
	s -> data = e;
	s -> next = p -> next;
	p -> next = s;
	return true;
}
//
指定节点的前插操作

如果用在节点的前驱节点后插入的想法去实现,就会使实现非常困难,在此用到一个非常巧妙的方法,值交换

typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
//在p节点前插入(先后插再交换值)
bool InsertNextNode(LNode *p,int e){
	if(p == NULL)
		return false;
	LNode *s = (LNode *)(malloc(sizeof(LNode));
	s -> next = p ->next;
	p -> next = s;
	s -> value = p;
	p -> value = e;
	return true;
}

删除

按位序删除
//按位序删除(带头节点)
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
bool Delete(LinkList L,int i,int &e){
	if(i < 1)
		return false;
	for(int j = 0; j < i - 1 && L != NULL; j++){
		L = L->next;
	}
	if( L == NULL)
		return false;
	LNode p = (LNode *)malloc(sizeof(LNode));
	p = L -> next;
	if( p == NULL)
		return false;
	e = p -> data;
	L -> next = p -> next;
	free(p);
	return true;
}       
指定节点的删除
typedef struct LNode{
	ElementType data;
	struct LNode;	
}LNode,*LinkList;
bool Delete(LNode *p){
	LNode *q = p -> next;
	if(*q == NULL){
		free(p);
		return true;
	}	
	p -> data = q -> data;
	p -> next = q -> next;
	free(q);
	return true;
}

查找

按位查找
typedef struct LNode{
	elementType data;
	struct LNode *next;
}LNode, *LinkList;
//按位查找,返回第i位元素(带头节点)
LNode *GetElem(LinkList L,int i){
	if( i < 1 )
		return NULL;
	for(int j = 0;j < i - 1 && L != NULL;j++){
		L = L -> next;
	}
	return  L;
}	
按值查找
typedef struct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
LNode *GetElem(LinkList L,int e){
	for(;L != NULL&& L -> data != e;L = L->next);
	return L;
}

求表长

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

//带头节点
int Length(LinkList L){
	int len = 0;
	for(;L -> next != NULL;len ++);
	return len; 
}

建立

尾插法
typedef struct LNode{
	int 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;
}

//后插法带头节点不初始化
LinkList ListTailInsert(LinkList L){
	LNode *head = L;
	LNode *p = (LNode *)malloc(sizeof(LNode));
	p -> next = NULL;
	int x;
	scanf("%d",&x);
	while(x!=9999){
		LNode *p = (LNode *)malloc(sizeof(LNode));
		p -> data = x;
	    L -> next = p;
	    L = p;
	    scanf("%d",&x);	
	}
	return head;
}
头插法
//头插法带头节点带初始化
LintList ListHeadInsert(){
	LNode *L = (LNode *)malloc(sizeof(LNode));
	L -> next = NULL;
	int x;
	scanf("%d",&x);
	while(x != 9999){
		LNode *p = (LNode *)malloc(sizeof(LNode));
		p -> data = x;
		p -> next = L -> next;
		L -> next = p;
		scanf("%d",&x);
	} 
	return L;
}
头插法的应用,列表的逆转
typedef truct LNode{
	int data;
	struct LNode *next;
}LNode, *LinkList;
LinkList reverOrder(LinkList oriList){ 
	LinkList L = (LinkList) malloc(sizeof(LNode));
	p -> value = 
	L -> next = null;
	oriList = oriList -> next;
	while(oriList != NULL){
		LNode *p = (LNode *)malloc(sizeof(LNode));
		p -> value = oriList -> value;
		p -> next = L -> next;
		L -> next = p;
		oriList = oriList -> next;
	}
	return L;
}

双链表

每个节点有前驱和后继

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

初始化

//有头节点
typedef struct DNode{
	ElemType data;
	struct Lnode *prior,*next;
}DNode, *DLinkList;

bool InitDLL(DLinkList &L){
	L = (DNode *)malloc(sizeof(DNode));
	if(L == NULL)
		return false;
	h -> prior = NULL;
	h -> next = NULL;
	return ture;
}

//判空
bool Empty(DLinkList L){
	if(L -> next == NULL)
		return ture;
	return false;
}

插入

//在p节点后插入s节点
bool InsertNextDNode(DNode *p,DNode *s){
	if(p == NULL && s == NULL)	
	return false;
	if( p -> next != NULL)
		p -> next -> prior = s;
	s -> next = p -> next;
	s -> prior = p;
	p -> next = s;   
	return true;
}

删除

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,DLinkList;
//删除p节点的后继节点
bool DeleteNextDNode(DNode *p){
	if( p == NULL )return false;
	if( p -> next == NULL)return false;
	DNode *d = p -> next;
	p -> next = d -> next;
	if( d -> next != NULL)
		d -> next -> prior = p;
	free(d);
	return true;
}
//销毁表
void Destroy(DLinkList &L){
	while(L -> next != NULL)
		DeleteNextDNode(L);
	free(L);
	L = NULL;
}

遍历

typedef struct DNode{
	ElemType data;
	struct DNode *prior,*next;
}DNode,DLinkList;
bool NextTraver(DNode *p){
	if( p == NULL|| p -> next == NUL)return false;
	while(p -> next != NULL){
		p = p-> next;
	}
	return true;
}
bool PriorTraver(DNode *p){
	if(p == NULL || p -> next == NULL || p -> next -> next == NULL)return false;
	while(p -> front -> front != NULL){
		p = p-> prior;
	}
	return true;
}

循环链表

循环单链表

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 = L;
	return true;
}

//判断表尾
bool IsTail(LinkList L,LNode *p){
	if( p -> next == L)return true;
	return false;
}

循环双链表

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

//初始化循环双链表
bool InitDLL(DLinkList &L){
	L = (DNode *)malloc(sizeof(DNode));
	if(L == NULL)return false;
	L -> prior = L;
	L -> next =L;
	return true;
}

//判空
bool Empty(DLinkList L){
	if(L -> next == L)return true;
	return false;
}

//判断表尾
bool IsTail(DLinkList L,DNode *p){
	if(p -> next == L)return true;
	return false;
}

//在p后插入s
bool InsertCirDLL(DNode *p,DNode *s){
	if(p == NULL||s == NULL)return false;
	p -> next -> prior = s;
	
}

静态链表

单链表:各个节点在内存中星罗棋布,散落天涯。

静态链表:分配一整片连续的内存空间,各个节点集中安置(顺序表?)

静态链表的节点:

  • 数据项
  • 下一个元素的下标

定义及基本操作

#define MaxSize 10		//静态链表的最大长度
typedef struct {		//静态链表结构类型的定义
	Element data;		//存储数据元素
	int next;			//下一个元素的数组下标
}SLinkList[MaxSize];	
SLinkList L;
//初始化(head->next设为-1 el -> next设为-2)
bool Init(SLinkList L){
	if( L == NULL)return false;
	L[0] -> next = -1;
	for (int i = 1;i < MaxSize ;i++)
		L[i] -> next = -2;
	return ture;
}

顺序表和链表的比较

逻辑结构

都是线性表,都是线性结构

物理结构

  • 顺序表
    顺序存储
    优点:支持随机存取,存储密度高
    缺点:大片连续空间分配不方便,改变容量不方便
  • 链表
    链式存储
    优点:离散的小空间分配方便,改变容量方便
    缺点:不可随机存取,存储密度低

数据的运算/基本操作

创销、增删改查

创建

  • 顺序表
    需要分配一大片连续空间(数组)
  • 链表
    只需分配一个头节点,之后方便扩展

静态分配:静态数组(容量不可变)
动态分配:动态数组(malloc、free)容量可改变,但需要移动大量元素,时间成本高

销毁

  • 顺序表
    修改length = 0
    静态分配:系统自动回收
    动态分配:需要手动free
  • 链表
    依次删除各个节点(free)

增删

  • 顺序表
    时间复杂度O(n),时间开销来自于移动每一项后续的数据元素,如果数据元素很大,则开销巨大
  • 链表
    时间复杂度O(N),时间开销来自于查找前驱元素,查找后只需要修改指针即可。

改查

按位查找
  • 顺序表
    时间复杂度O(1)
  • 链表
    时间复杂度O(n)
按值查找
  • 顺序表
    遍历:时间复杂度O(n)
    若表内元素有序,可以二分查找
    二分查找:时间复杂度O(log(2,n))
  • 链表
    时间复杂度:O(n)

选择

  • 顺序表
    表长可预估,查询搜索操纵较多(增删操作较少)
  • 链表
    表长难以估计,经常增删元素

答题模板

问题:巴拉巴拉,用顺序表还是链表好?
顺序表和链表的逻辑结构都是线性结构,都属于线性表
但是两者的存储结构不同,线性表采用顺序存储,支持随机存取,存储密度高,但大片连续内存空间分配不方便,扩容不方便;链表采用链式存储,离散的小空间分配方便,扩容方便,但不可随机存取,存储密度低。
由于采用不同的存储方式实现,因此基本操作的实现效率不同。当初始化时…,当插入数据元素时…,当删除一个数据元素时…,当查找一个数据元素时…。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值