数据结构笔记2-3

知识总览
在这里插入图片描述

2-3.1单链表

单链表与顺序表的对比

在这里插入图片描述

单链表的创建

链表结构体声明

typedef struct Node
{
    int data;                       //数据域 每个节点存放一个数据
    struct Node *pNext;    //指针域 指针指向下一个节点
} NODE, *PNODE;
//NODE *    声明指向单链表的第一个节点的指针
//PNODE    声明指向单链表的第一个节点的指针--可读性更强
不带头结点与带头结点的空链表初始化
//不带头结点的空链表初始化
LinkList NHInitList(){
    return NULL;    //空表,没有数据
}

//带头结点的空链表初始化
LinkList HInitList(){
    LinkList L = NULL;
    L = (LinkList)malloc(sizeof(LNode));
    if(L=NULL){
        printf("创建失败!\n");
        exit(-1);
    }
    L->pNext = NULL;
    return L;
}
头插法创建单链表

代码实现

//带头结点的头插法创建单链表
LinkList ListCreatHeadInsert()
{
    int len;                    //所需创建的链表长度
    LNode *s;               //暂存新建节点指针
    LinkList pHead;     //返回的创建的链表头节点指针
    //创建头节点
    pHead = (LNode *)malloc(sizeof(LNode));

    if(pHead==NULL)
    {
        printf("分配内存失败!\n");
        exit(-1);
    }

    printf("<=========头插法=========>\n");
    printf("请输入需要生产的节点数:\n");
    scanf("%d", &len);
    for (int val,i = 0; i < len;i++)
    {
        printf("请输入第%d个节点的值:", i + 1);
        scanf("%d", &val);
        s = (LinkList)malloc(sizeof(LNode));  //分配空间给新节点
        if(s==NULL)
        {
            printf("分配内存失败!\n");
            exit(-1);
        }
        s->data = val;                                  //赋值给新创建的节点
        s->pNext = pHead->pNext;          //新节点接管头节点的后继节点
        pHead->pNext = s;                       //新节点成为头节点的后继节点
    }
    return pHead;                                   //返回头节点
}

重要应用:链表逆序

//利用头插法逆序带头结点的链表
LinkList Reverse(LinkList pHead){
    if(HEmpty(pHead))
        return pHead;
    LinkList p = HInitList();           //初始化空的带头结点链表
    while(!HEmpty(pHead)){
        pHead = pHead->pNext;  //链表指向下一个节点,注意,一开始进来需要跳过头节点,
        					   //因为头节点不带数据
        LNode *s = (LNode *)malloc(sizeof(LNode));//为新节点分配空间
        s->data = pHead->data;//赋值
        s->pNext = p->pNext;
        p->pNext = s;
    }
    return p;                  //返回逆序后的头节点
}
尾插法创建单链表

代码实现

// 带头节点的尾插法创建单链表
LinkList ListCreatTailInsert(){
    int len;                    //所需创建的链表长度
    LNode *s, *r;          // s:暂存新建节点指针,r保存当前的尾节点的指针
    LinkList pHead;     //返回的创建的链表头节点指针
    //创建头节点
    pHead = (LNode *)malloc(sizeof(LNode));
    r = pHead;                                          //尾指针指向pHead节点,因为是新建立的,故为最后一个节点
    if(pHead==NULL)
    {
        printf("分配内存失败!\n");
        exit(-1);
    }
    printf("<=========尾插法=========>\n");
    printf("请输入需要生产的节点数:\n");
    scanf("%d", &len);
    for (int val,i = 0; i < len;i++)
    {
        printf("请输入第%d个节点的值:", i + 1);
        scanf("%d", &val);
        s = (LinkList)malloc(sizeof(LNode));  //分配空间给新节点
        if(s==NULL)
        {
            printf("分配内存失败!\n");
            exit(-1);
        }
        s->data = val;                                  //赋值给新创建的节点
        s->pNext = NULL;                           //新节点的后继节点置空
        r->pNext = s;                                  //尾节点的后继节点指向新节点
        r = s;                                              //尾节点指向新节点
    }
    /*
        也可以将上面的语句 s->pNext = NULL 从循环中删除
        改成在循坏外最后执行:r->pNext = NULL;
    */
    return pHead;                                   //返回头节点
}
小节总结

image-20220605154923264

单链表的插入与删除

知识总览

image-20220605170604374

按位序插入(带头结点)

image-20220605171328495

代码实现

/** 带头结点的单链表按位序插入
 * @param L 操作的链
 * @param i 需要插入的位序
 * @param e 需要插入的元素
 * 头节点作为第0号节点,而插入第 i 位节点可看成插入第 i -1 节点之后
 * 故我们需要定位到 第 i - 1 节点(因为链表插入到某个节点后,需要要知道该节点的前继节点)
 * 下面定位节点的 for 循环功能每次执行完一次,指针都会指向到下一个下一个节点
 * 因此如果我们想要当最后一次执行完for循环之后能正确的指向我们所需的节点位置( 第i - 1节点)
 * 我们就得需要却确保最后一次进入for循环时遍历到的是第 i -2 个节点
 * 故下面for循环的终止条件为 j< i-1 <=> j<= i - 2
 * */
bool HListInsert(LinkList *L, int i, int e){
    if(i<i)                 //判断位序的合法性
        return false;
    LNode *p;      		   //指针p记录当前扫描到的节点
    int j = 0;            //当前p指向的是第几个节点
    p = L;               //L指向头节点,头节点是第0个节点(不存数据)
    //循环找到第 i-1 个节点
    while(p!=NULL&&j<i-1){
        p = p->pNext;
        j++;
    }

    if(p==NULL)//i值不合法
        return false;
    //分配新节点空间
    LNode *s = (LNode *)malloc(sizeof(LNode));
    s->data = e;                    //赋值给新节点
    s->pNext = p->pNext;   //将p节点看成头节点
    p->pNext = s;                //使用 头插法
    return true;
}

几种情况:

  • 插入表头: i = 0

image-20220606171955686

  • 插入表中:0<i<length

    image-20220606173542456

  • 插入表尾:i=length

    其逻辑与插入表中类似

    image-20220606174020179

  • 插入表外:i>length

    image-20220606174522173

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

image-20220607135722737

代码实现

/** 不带头结点的按序插入
 * @param L 需要操作的链表                    //L指向的是第一个节点元素指针的指针
 * @param i 需要插入的位序
 * @param e 需要插入的元素
 * 注意,由于不带头结点,插入第一位时需要修改头指针指向,所以传参数时
 * 不能值传递,只能指针传递,即传递指针的指针
 * */
bool NHListInsert(LinkList *L, int i, int e)  {
    if(i<1)
        return false;                                                           //判断位序的合法性
    //因为不带头结点,故需指要单独判断和操作。具体的,让新节点插入表头
    //并让表头指针指向该节点
    LNode *s = (LNode *)malloc(sizeof(LNode));      //给新结点分配空间
    if(i==1){
        s->data = e;                                                        //给新结点赋值
        s->pNext = (*L);                                                     //将头节点插入表头
        (*L) = s;                                                                  //将头指针的指向新节点
        return true;
    }

    /* 以下代码段同头插法的寻找第 i-1 个节点 */
    LNode *p = *(L);                                                         //指针p指向当前扫描到的节点
    int j = 1;                                                                //当前p指向的第几个几点,注意,由于是不带头节点的,故从1开始
    while(p!=NULL&&j<i-1){
        p = p->pNext;
        j++;
    }
    if(p==NULL)                                                        //i值不合法,即i大小超过当前链表长度
        return false;
    s->data = e;
    s->pNext = p->pNext;
    p->pNext = s;
    return true;
}

需要特别注意的是:i=1插入表首的情况

image-20220607161747971

其他情况与带头结点一致:

image-20220607161917729
指定节点的后插操作
image-20220607162019473

代码实现

/** 插入到指定节点 p 后面
 * @param P 指定的节点
 *  @param e 需要插入的元素
 * */
bool HInsertNextNode(LNode *p, int e){
    if(p==NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s==NULL)
        return false;
    s->data = e;
    s->pNext = p->pNext;        //后插
    p->pNext = s;
    return true;
}
指定节点的前插操作

解法一:

/** 将元素 e 前插到指定节点 p 之前
 * @param L 需要菜操作的链表头指针
 * @param P 指定的节点
 * @param e 需要插入的元素
 **/
bool HInsertPriorNode(LinkList L, LNode *p, int e){
    //如果需要插入到头节点之后,即需要充当第一个节点
    if(L==p){
        LNode *s = (LNode *)malloc(sizeof(LNode));
        s->data = e;
        s->pNext = L->pNext;        //1-1使用头插法将s插到头节点L之后
        L->pNext = s;                     //1-2使用头插法将s插到头节点L之后
        return true;
    }
    LNode *r = L->pNext;                   //创建尾指针 初始化为指向头节点
    /* r指针寻找目标节点:p  ;L指针充当r的前继结点的指针   */
    while(r!=NULL){
        //如果 r 指针找到目标节点 p
        if(r==p){
            LNode *s = (LNode *)malloc(sizeof(LNode));
            s->data = e;
            s->pNext = L->pNext;                //  1-1将s节点接到L节点后面
            L->pNext = s;                             //  1-2将s节点接到L节点后面
            return true;
        }
        //如果没有找到,继续进行指针的传递
        else{
            L = r->pNext;
            r = r->pNext;
        }
    }
    //没有找到p,结束
    return false;
}
image-20220607170510011

解法二:

/** 将元素 e 前插到指定节点 p 之前
 * @param   p 指定的节点
 * @param e 需要插入的元素
 * */
bool HInsertPriorNodeC(LNode *p, int e){
    if(p==NULL)
        return false;
    LNode *s = (LNode *)malloc(sizeof(LNode));
    if(s==NULL)
        return false;
    s->pNext = p->pNext;        //1.1将新节点插入到指定节点p之后
    p->pNext = s;                     //1.2将新节点插入到指定节点p之后
    s->data = p->data;            //2.1交换s与p两节点的数据
    p->data = e;                      //2.2交换s与p两节点的数据
    return true;
}

image-20220607170637272

按位序删除(带头结点/不带头结点)

在这里插入图片描述

代码实现

/** 带头结点删除位序为 i 的节点 并将删除的元素赋给 e
 *  @param L 需要操作的链表
 * @param i 需要删除的节点位序
 * @param e 将删除的值赋给e
 * */
bool HListDelete(LinkList L, int i, int *e){
    if(i<1)                 //判断i的范围合法性
        return false;
    LNode *p;           //指针p指向的是当前扫描的节点
    p = L;                 //开始指向头节点
    int j = 0;            //L指向头节点,头结点是第0个节点,不存数据
    //循环找到第 i-1 个节点
    while(p!=NULL&&j<i-1){
        p = p->pNext;
        j++;
    }
    if(p==NULL)                             //如果 i 值不合法
        return false;
    if(p->pNext==NULL)              //第 i-1 个节点之后已无其他节点
        return false;
    LNode *q = p->pNext;          //令q指向被删除的节点
    *e = q->data;
    p->pNext = q->pNext;        //将*q节点从从链中“断开”
    free(q);                                 //释放节点的存储空间
    return true;                          //删除成功
}
image-20220608145012819

不带头结点

/** 不带头结点删除位序为 i 的节点 并将删除的元素赋给 e
 *  @param L 需要操作的链表
 * @param i 需要删除的节点位序
 * @param e 将删除的值赋给e
 * */
bool NHListDelete(LinkList *L, int i, int *e){
    if(i<1||L==NULL)                 //判断 i 与 指针 的合法性
        return false;
    if(i==1){
        LNode *q = (*L);
        *e = q->data;
        *L = q->pNext;
        free(q);
        return true;
    }
    //<<=====余下操作同带头结点的====>>//
    LNode *p;           //指针p指向当前扫描的节点
    p = *L;                //开始指向首节点
    int j = 1;             //L指向首节点,首节点是第1个节点
    //循环找到第 i-1 个节点
    while(p!=NULL&&j<i-1){
        p = p->pNext;
        j++;
    }
    if(p==NULL)                             //如果 i 值不合法
        return false;
    if(p->pNext==NULL)              //第 i-1 个节点之后已无其他节点
        return false;
    LNode *q = p->pNext;          //令q指向被删除的节点
    *e = q->data;
    p->pNext = q->pNext;        //将*q节点从从链中“断开”
    free(q);                                 //释放节点的存储空间
    return true;                          //删除成功
}
指定节点的删除
image-20220608152147697

方法一:传入头指针,查找所需删除节点p的前继结点

代码实现:

/** 删除指定节点 方法一:查找前继结点
 * @param L 需要操作的链表
 *  @param p 需要删除的节点
 * */
bool HDeleteNode(LinkList L, LNode *p){
    LNode *r = L->pNext;                //设置扫描尾指针,指向当前扫描到的值,初始化为第一个节点
    //查找需要删除的节点的前继结点 用L指向;此时r匹配到需要删除的节点
    while(r!=NULL&&r!=p){
        L = r;                                //1-1进行指针传递
        r = r->pNext;                         //1-2进行指针传递
    }
    if(r==NULL)
        return false;
    L->pNext = r->pNext;           			//将指针r从链表中“断开”
    free(r);
    return true;
}

方法二:与后继节点交换数据,并将后继节点删除

代码实现:

/** 删除指定节点
 * 交换p节点与其后继节点之间的数据,然后将后继节点删除
 * @param p 需要删除的节点
 * */
bool HDeleteNode1(LNode *p){
    if(p==NULL)
        return false;
    LNode *q = p->pNext;            //令q指向*p的后继节点
    p->data = p->pNext->data;   //和后继节点交换数据   注意,如果p节点为最后一个节点的话,该语句会出错
    p->pNext = q->pNext;            // 将*q节点从链中"断开"
    free(q);                                     //释放后继节点的存储空间
    return true;
}
image-20220608153639317

注意,该方法不能删除最一个节点

image-20220608154219747
小节总结

在这里插入图片描述

单链表的查询

注意,以下代码是建立在包含头节点的链表中实现的

image-20220608155609406
按位查询

代码实现;

image-20220608160618905
/** 删除指定节点
 * 交换p节点与其后继节点之间的数据,然后将后继节点删除
 * @param p 需要删除的节点
 * */
bool HDeleteNode1(LNode *p){
    if(p==NULL)
        return false;
    LNode *q = p->pNext;            //令q指向*p的后继节点
    p->data = p->pNext->data;   //和后继节点交换数据        注意,如果p节点为最后一个节点的话,该语句会出错
    p->pNext = q->pNext;            // 将*q节点从链中"断开"
    free(q);                                     //释放后继节点的存储空间
    return true;
}

/**  按位查找,返回第 i 个节点元素(包含头节点)
 * @param L 需要操作的链表
 *  @param i 需要返回的节点位序
 * */
LNode *HGetElem(LinkList L, int i){
    if(i<1)                             //判断位序的合法性
        return NULL;
    LNode *p = L;               //p指针指向当前扫描到的节点元素
    int j = 0;                      //p初始化为指向头节点,头节点属与第0号节点
    //循环找到第 i 个节点
    while(p!=NULL&&j<i){
        p = p->pNext;
        j++;
    }
    return p;
}
按值查询
image-20220608160710352
/**  按位查找,返回第 i 个节点元素(包含头节点)
 * @param L 需要操作的链表
 *  @param i 需要返回的节点位序
 * */
LNode *HGetElem(LinkList L, int i){
    if(i<1)                             //判断位序的合法性
        return NULL;
    LNode *p = L;               //p指针指向当前扫描到的节点元素
    int j = 0;                      //p初始化为指向头节点,头节点属与第0号节点
    //循环找到第 i 个节点
    while(p!=NULL&&j<i){
        p = p->pNext;
        j++;
    }
    return p;
}

/** 按值查找,返回值=e的节点(包含头节点)
 *  @param L 需要操作的链表
 * @param e 需要查找的元素
 * */
LNode *HLocateElem(LinkList L, int e){
    LNode *p = L->pNext;
    //从第1个节点开始查找数据域为e的节点
    while(p!=NULL&&p->data!=e){
        p = p->pNext;
    }
    return p;       //      若找到则返回对应的节点,否则返回NULL
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值