一、单链表
定义:每个结点除了存放数据元素外,还要存储指向下一个节点的指针
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);
//....后续代码
}
3.单链表的插入和删除
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;//插入表头
i = 3;//插在表中
i = 5;//插在表尾
i = 6;//return false
平均时间复杂度: O(n)
3.2按位序插入(不带头结点)
①如果 i = 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;
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…
3.3指定结点的前插操作
①传入头指针
方法:循环查找p的前驱q,再对q后插
bool InSertPrioNode(LinkList L,LNode *p,ElemType e)
②不传入头指针
例:
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;
}
例:
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按位序删除(带头结点)
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.小结
单链表的局限性:无法逆向检索,有时候不太方便
二、双链表
1.双链表的初始化(带头结点)
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.双链表的插入(带头结点)
警告:如果p是最后一个结点,则需要做如下判断…
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.双链表的删除(带头结点)
//删除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)