数据结构学习笔记(2)--链表

1.单链表

优点:不要求大片连续空间,改变容量方便。

缺点:不可随机存取,要耗费一定空间存放指针。

用代码定义一个链表

struct LNode{
    ElemType data;    //数据域
    struct LNode *next;    //指针域
}

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

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

typedef给数据类型重命名

typedef struct LNode{
    ElemType data;
    struct LNode *next;
}LNode,*LinkList;    //将单链表名字定义为LNode,LinkList为一个指向struct LNode的指针

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

LNode * L;或 LinkList L;这两种表示方法本质上是一样大,但是为了使代码的可读性更强,我们往往使用LNode *强调返回的是一个结点,LinkList强调这是一个单链表。

(1)不带头结点的单链表

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

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

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

(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;
}

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

(3)单链表的插入

按位序插入(带头结点)

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)    //i值不合法
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;    //将结点s连到p之后
    return true;    //插入成功
}

注意:这两行代码顺序不能写反

s->next = p->next;
p->next = s;

按位序插入(不带头结点)

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

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;    //p指向第一个结点(不是头结点)
    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;    //插入成功
}

指定结点的后插操作

//后插操作:在p结点之后插入元素e
bool InsertNode (LNode *p,ElemType e){
    if(p==NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if (s==NULL)    //若内存不足可能分配失败
        return false;
    s->data = e;    //用结点s保存数据元素e
    s->next=p->next;
    p->next=s;    //将结点s连到p之后
    return true;
}

指定结点的前插操作

方法一:

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连到p之后
    s->data = p->data;    //将p中元素复制到s中
    p->data = e;    //p中元素覆盖为e
    return true;
}

方法二:

bool InsertPriorNode(LNode *p, LNode *s){
    if(p == NULL||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;
}

(4)单链表的删除

按位序删除(带头结点)

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

指定结点的删除

//删除指定结点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;    //将*q结点从链中断开
    free(p);    //释放后继结点的存储空间
    return true;
}

如果p是最后一个结点只能从表头开始依次寻找p的前驱。

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

(5)单链表的查找

按位查找

//按位查找,返回第i个元素(带头结点)
LNode * GetElem(LinkList L,int i){
    if(i<0)
        return NULL;
    LNode *p;    //指针p指向当前扫描到的结点
    int j=0;    //当前p指向的是第几个结点
    p = L;
    while(p!=NULL && j<i){
        p = p->next;
        j++;
    }
    return p;
}

 按值查找

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

(6)求单链表的长度

//求表的长度
int length(LinkList L){
    int len = 0;    //统计表长
    LNode *p = L;
    while(p->next !=NULL){
        p=p->next;
        len++;
    }
    return len;
}

(7)单链表的建立

step1:初始化一个单链表

step2:每次取一个数据元素,插入到表尾/表头

①尾插法

先初始化单链表

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;
}

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

设置变量length记录链表长度

使用while循环

while{
    //每次取一个数据元素e;
    ListInsert(L,length+1,e);  //插到尾部;
    length++;
}

尾插法建立单链表完整代码:

LinkList List_Tailnsert(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;
}

②头插法

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);
    }
}

注意:头插法的重要应用:链表的逆置

2.双链表

(1)双链表既有后继指针也有前驱指针

typedef struct DNode{    //定义双链表结点类型
    ElemType data;    //数据域
    struct DNode *prior,*next;    //前驱指针和后继指针
}DNode,*DLinkList;

(2)双链表的初始化

typedef struct DNode{
    ElenType 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;    //头结点之后暂时还没有结点
}

void testDLinkList(){
    //初始化双链表
    DLinkList L;
    InitDLinkList(L);
    //后续代码
}

(3)双链表的插入

//在p结点之后插入s结点
bool InsertNextNode(DNode *p, DNode *s){
    if(p == NULL || s == NULL)    //非法参数
        return false;
    s->next = p->next;    //将结点*s插入到结点*p之后
    if (p->next != NULL)    //如果p结点有后继结点
        p->next->prior = s;
    s->prior = p;
    p->next = s;
}

(4)双链表的删除

//删除p结点的后继结点
bool DeleteNextDNode(DNode *p){
    if (p == NULL)
        return false;
    DNode *q = p->next;    //找到p的后继结点q
    if (q == NULL)
        return false;    //p没有后继
    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;    //头指针指向NULL
}

(5)双链表的遍历

①后向遍历

while(p!=NULL){
    //对结点p做相应处理,如打印
    p = p->next;
}

②前向遍历

while(p!=NULL){
    //对结点p做相应处理
    p = p->prior;
}

③前向遍历(跳过头结点)

while(p->prior != NULL){
    //对结点p做相应处理
    p=p->prior;
}

双链表不可随机存取,按位查找、按值查找操作都只能用遍历的方式实现。

3.循环链表

(1)循环单链表

在初始化时头结点的next指针需要指向它自己。

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;    //头结点next指向头结点
    return true;
}

//判断循环单链表是否为空
bool Empty (LinkList L){
    if(L->next == L)
        return true;
    else
        return false;
}

//判断结点p是否为循环单链表的表尾结点
bool isTail(LinkList L,LNode *p){
    if (p->next == L)
        return true;
    else
        return false;
}

(2)循环双链表

表头结点的prior指向表尾节点,表尾结点的next指向头结点。

循环双链表的初始化

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 = L;    //头结点的prior指向头结点
    L->next = L;    //头结点的next指向头结点
    return true;
}

void testDLinkList(){
    //初始化循环双链表
    DLinkList L;
    InitDLinkList(L);
    //...后续代码...
}

//判断循环双链表是否为空
bool Empty(DLinkList L) {
    if (L->next == L)
        return true;
    else
        return false;
}

//判断p结点是否为循环双链表的表尾结点
bool isTail (DLinkList L,DNode *p){
    if (p->next == L)
        return true;
    else
        return false;
}

双链表的插入

//在p结点之后插入s结点
bool InertNextDNode(ONode *p,DNode *s){
    s->next = p->next;
    p->next->prior = s;
    s->prior = p;
    p->next = s;
}

双链表的删除

//删除p的后继结点q
p->next = q->next;
q->next->prior = p;
free(q);

4.静态链表

(1)定义一个静态链表

定义一个结点

#define MaxSize 10    //静态链表的最大长度
struct Node{    //静态链表结构类型的定义
    ElemType data;    //存储数据元素
    int next;    //下一个元素的数组下标
};

定义多个连续存放的结点

void testLinkList(){
    struct Node a[MaxSize];
    //...后续代码
}

一个特殊的定义方法

#define MaxSize 10
typedef struct{
    ElemType data;
    int next;
}SLinkList[MaxSize];

在初始化的时候可以让next为某个特殊值,如-2,防止“脏数据”

(2)简述基本操作的实现

①初始化静态链表:

把a[0]的next设为-1

②查找某一个位序的结点:

从头结点出发挨个往后遍历结点

③插入位序为i的结点:

找到一个空的结点,存入数据元素;

从头结点出发找到位序为i-1的结点;

修改新结点的next为-1;

修改i-1号结点的next

注意:静态链表的容量固定不变

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值