单链表的定义
typedef struct LNode{
ElemType data;//数据域
struct LNode *next;//指针域
}LNode,*LinkList;
说明(上面两种重命名方式)
作用是相同的,理解上不同
LNode
:强调这是一个结点,指向一个结点的指针就代表这个结点(在创建一个新的结点时会用到)LNode s = (LNode *)malloc(sizeof(LNode));
* LinkList
:强调这是一个链表,一般指向一个链表的首结点,就代表了这个链表,(在创建一个链表时会用到)LinkList L = (LinkList)malloc(sizeof(LNode));
头插法
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;
scanf("%d",&x);
}
return L;
}
头插法经常用于链表逆置
尾插法
LinkList List_TailInsert(LinkList &L){
int x;
LNode *s,*r=L;//r为表尾指针
scanf("%d",&x);
while(x!=9999){
s=(LNode *)malloc(sizeof(LNode));
s->data=x;
r->next=s;//尾插法
r=s;
scanf("%d",&x);
}
r->next=NULL;//尾结点置空,防止脏数据
retuen L;
}
按序号查找
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 && j<i){//表长之内寻找第i个结点
p=p->next;
j++;
}
return p;
}
按值查找
LNode &GetElem(LinkList L,ElemType e){
LNode *p = L->next;
while(p!=NULL && p->data!=e){
p=p->next;
}
retuen p;
}
按位序插入
带头结点
//在第i个位置插入元素e
bool ListInsert(LinkList &L,int i,ElemType e){
if(i<1)//位序从1开始
return false;
LNode *p;//指向当前扫描的结点
int j=0;//当前p指向的是第几个结点
p=L;//p指向头结点,头结点是第0个数据
while(p!=NULL && j<i-1){//循环找到i-1个结点
p=p->next;
j++;
}
if(p==NULL){//i值不合法
return false;
}
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data=e;
s->next=p-next;
p->next=s;
return true;
}
不带头结点
这里就可以理解添加头结点的作用了,操作不需要移动头指针
if(i==1){//插入第一个节点的操作不同,需要移动头指针
LNode *s = (LNode *)malloc(sizeof(LNode));
s->data = e;
s->next = L;
L = s;//移动头指针指向第一个
return true;
}
按位序删除
bool ListDelete(Linklist &L,int i,ElemType &e){//用e返回第i个位置删除的结点
if(i<1){
return false;
}
LNode *p;
int j=0;//当前p指向的是第几个结点
p = 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;
p->next = q->next;
free(q);//释放空间
return true;
}
双链表
结点中有两个指针分别指向其前驱和后继节点
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;//前驱和后继指针
}DNode,*DLinkList;
//初始化
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode));
if(L==NULL)//内存不足
return false;
L->prior = NULL;//头结点的prior永远指向NULL
L->next = NULL;
}
typedef struct DNode{
ElemType data;
struct DNode *prior,*next;//前驱和后继指针
}DNode,*DLinkList;
//初始化
bool InitDLinkList(DLinkList &L){
L = (DNode *)malloc(sizeof(DNode));
if(L==NULL)//内存不足
return false;
L->prior = NULL;//头结点的prior永远指向NULL
L->next = NULL;
}
循环链表
最后一个结点的指针指向第一个结点
//初始化一个循环单链表
bool InitList(LinkList &L){
L (LNode *)malloc(sizeof(LNode));
if(L==NULL)
return false;
L->next = L;
return true;
}
- 循环单链表是一个“环”,因此在任何一个位置上插入和删除的操作都是等价的,无需判断是否是表尾
- 若设头指针,则对表尾操作的时间复杂度是
O(n)
- 若设尾指针
r
,r->next
即是头指针,则对表头表尾进行操作都只需要O(1)
静态链表
借助数组来描述线性表的链式存储结构
-
数据域
data
,指针域next
,这里的指针是结点的相对地址(数组下标),游标 -
需要预先分配一块连续签的内存空间
//第一种
#define MaxSize 50
typedef struct{
ElemType data;
int next;
}SLinkList[MaxSize];
//第二种
#define MaxSize 50
struct Node{
EmelType data;
int next;
};
typedef struct Node SLinkList[MaxSize];
//两种方式等价
//声明一个静态链表
SLinkList b;//50个结点类型大小的数组
链表逆置
单链表就地逆置,辅助空间复杂度
O(1)
//方法一:将头结点摘下,然后从第一结点开始,依次插入到头结点的后面(头插法)
LinkList Reverse_1(LinkList L){
LNode *p,*r;
p=L->next;
L->next=NULL;//尾指针为NULL,脏数据原因
while(p!=NULL){
r=p->next;//暂存p的后继,防止断链
p->next=L->next;//头插法
L->next=p;
p=r;//p后移
}
}
//方法二:思想就是将next指针指向其前驱结点,结点指针反转
LinkList Reverse_2(LinkList L){
LNode *pre,*p=L->next,*r=p->next;//使用3个连续指针
p->next=NULL;//第一个结点反转之后成了最后一个结点
while(r!=NULL){//r为空,说明p为最后一个结点
pre=p;
p=r;
r=r->next;
p->next=pre;//指针反转
}
L->next=p//最后一个结点
return L;
}