第二章 数据结构-单链表
一、定义
1.概念
单链表:链式存储。每个节点除了存放数据元素外,还要存储指向下一个节点的指针。
优点:不要求大片连续空间,改变容量方便。
缺点:不可随机存取,要耗费一定空间存放指针。
2.代码实现
2.1 单链表定义
struct LNode{ //定义单链表节点类型
ElemType data; //每个节点存放一个数据元素 数据域
struct LNode *next; //指针指向下一个节点 指针域
}
struct LNode *p=(struct LNode *)malloc(sizeof(struct LNode));
//增加一个新的结点:在内存中申请一个结点所需空间,并用指针p指向这个结点
2.2 不带头结点的单链表
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);
}
2.3 带头结点的单链表
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);
}
ps:头结点不存储数据
LinkList
等价于LNode *
,前者强调这是链表,后者强调这是结点。
二、插入与删除
1.插入操作
1.1 按位序插入(带头结点)
ListInsert(&L,i,e)
:插入操作。在表L中第i个位置上插入指定元素e。
代码:
//在表L中第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; //i值不合法
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
平均时间复杂度: O ( n ) O(n) O(n)
1.2 按位序插入(不带头结点)
ListInsert(&L,i,e)
:插入操作。在表L中第i个位置上插入指定元素e。
代码:
//在表L中第i个位置上插入指定元素e(带头结点)
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1) return false;
if(i==1){ //插入第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; //i值不合法
LNode *s=(LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
1.3 指定结点的后插操作
//后插操作:在p结点之后插入元素e
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;
}
时间复杂度: O ( 1 ) O(1) O(1)
1.4 指定结点的前插操作
//前插操作:在p结点之前插入元素e
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=s;
return true;
}
时间复杂度: O ( 1 ) O(1) O(1)
2.删除操作
2.1 按位序删除(带头结点)
ListDelete(&L,i,&e)
:删除操作。删除表L中第i个位置的元素,并用e返回删除元素的值。
bool ListDelete(LinkList &L,int i,ElemType &e){
if(i<1) return false;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=L;
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
if(p==NULL) return false; //i值不合法
if(p->next==NULL) return false; //i-1个结点之后已无其他结点
LNode *q=p->next; //令q指向被删除结点
e=q->data; //用e返回元素的值
p->next=q->next;
free(q);
return true;
}
时间复杂度: O ( n ) O(n) O(n)
2.2 指定结点的删除
//删除指定结点p
bool DeleteNode(LNode *p){
if(p==NULL) return false;
LNode *q=p->next; //令q指向*p的后继结点
p->data=p->next->data; //和后继结点交换数据域
p->next=q->next;
free(q);
return true;
}
时间复杂度: O ( 1 ) O(1) O(1)
三、查找
1.按位查找
//按位查找,返回第i个元素(带头结点)
LNode *GetElem(LinkList L,int i){
if(i<1) return false;
LNode *p; //指针p指向当前扫描到的结点
int j=0; //当前p指向的是第几个结点
p=L;
while(p!=NULL && j<i-1){
p=p->next;
j++;
}
return p;
}
时间复杂度: O ( n ) O(n) O(n)
2.按值查找
//按值查找1,找到数据域等于e的结点
LNode *LocateElem(LinkList L,ElemType e){
LNode *p=L->next;
while (p!=NULL && p->data!=e){
p=p->next;
return p;
}
}
时间复杂度: O ( n ) O(n) O(n)
本文为个人学习笔记,若有问题可一起讨论学习
更多内容可以关注个人博客:Ackow的博客