数据结构学习(days03)

双向链表与两个指针

双向链表的每个节点包含两个指针:一个指向前驱节点(prev),另一个指向后继节点(next)。这种结构支持双向遍历,但需注意指针维护,避免内存泄漏或断裂。

Makefile 时间戳机制

Makefile 通过比较目标文件和依赖文件的时间戳决定是否重新编译。若 obj.c 的时间戳晚于对应的 .o 文件,则触发重新编译;否则跳过。

关键规则示例

main: main.o utils.o
    gcc -o main main.o utils.o

main.o: main.c utils.h
    gcc -c main.c

utils.o: utils.c utils.h
    gcc -c utils.c

Makefile 语法规范

        文件名与对齐:第一行通常声明目标,第二行用 Tab 缩进规则。

        目标与依赖:冒号前为目标文件,冒号后为依赖文件。

        变量定义:支持 =(赋值)和 +=(追加)。

        清理操作clean 目标用于删除临时文件。

变量与路径示例

CC = gcc
INC = ./include 
SRC = main.c utils.c
OBJ = app

$(SRC): $(OBJ)
    $(CC) $^ -o $@  -I$(INC)
clean:
    rm $(OBJ)

 -I 指定头文件目录,-L 指定库文件目录。$@ 表示当前目标文件名,$^表示依赖文件。

声明链表的数据,结点,表头。

typedef struct studet
{
    int id;
    char name[32];
    int score;
}DataType;

typedef struct node
{
    DataType data;
    struct node *ppre;
    struct node *pnext;
}Node;

typedef struct doulink
{
    Node *phead;
    int clen;
}PdLink;
typedef enum {FORWARD, BACKWARD} DIR;

头插

        核心逻辑:链表为空,则直接在表头后边插入;链表不为空,需要将待插入结点的next指向表头指向的结点,表头指向结点的prv指向待插入结点,表头指向待插入结点。

void InsertHeadDoulink(PdLink *pdlink,DataType data)
{
    Node *pnode = malloc(sizeof(Node));
    if(NULL == pnode)
    {
        printf("malloc pnode error\n");
    }
    pnode->data = data;
    pnode->pnext = NULL;
    pnode->ppre = NULL;

    if(Isempty(pdlink))
    {
        pdlink->phead = pnode;
    }
    else
    {
        pnode->pnext = pdlink->phead;
        pdlink->phead->ppre = pnode;
        pdlink->phead = pnode;
    }
    pdlink->clen++;

双向链表遍历

        既可以向后遍历,又可以向前遍历。

void ShowDouLink(PdLink *pdlink,DIR dir)
{
    if(Isempty(pdlink))
    {
        printf("empty\n");
        return ;
    }
    Node *temp = pdlink->phead;
    if(dir == FORWARD)
    {
        while(temp)
        {
            printf("%d %s %d\n",temp->data.id, temp->data.name, temp->data.score);
            temp = temp->pnext;
        }
    }
    else if(dir == BACKWARD)
    {
        while(temp->pnext)
        {
            temp = temp->pnext;
        }
        while(temp)
        {
            printf("%d %s %d\n",temp->data.id, temp->data.name, temp->data.score);
            temp = temp->ppre;
        }
    }

双向链表尾插

        核心逻辑:链表为空,则直接在表头后边插入;链表不为空,需要先遍历,找到最后一个,待插结点的prv指向最后一个结点,最后一个结点的next指向待插结点。

void InsertTailDouLink(PdLink * pdlink,DataType data)
{
    Node *pnode = malloc(sizeof(Node));
    if(NULL == pnode)
    {
        printf("malloc Tail error\n");
        return ;
    }
    pnode->data = data;
    pnode->pnext = NULL;
    pnode->ppre = NULL;

    if(Isempty(pdlink))
    {
        pdlink->phead = pnode;
    }
    else
    {
        Node *temp = pdlink->phead;
        while(temp->pnext)
        {
            temp = temp->pnext;
        }
        temp->pnext = pnode;
        pnode->ppre = temp;
    }
    pdlink->clen++;
}

头删

        核心逻辑:链表中的结点为1,直接释放空间,并且将phead置空,链表当前长度 pdlink->clen 减 1;结点为多个,将临时指针 temp 指向链表的当前头节点 pdlink->phead,将链表的头节点指针 pdlink->phead 更新为原头节点的下一个节点 temp->pnext,释放原头节点 temp 占用的内存空间,将新头节点的前驱指针 ppre 设为 NULL,表示它现在是链表的第一个节点,链表当前长度 pdlink->clen 减 1。

void DeleteHeadDouLink(PdLink *pdlink)
{
    if(Isempty(pdlink))
    {
        printf("empty\n");
        return ;
    }
    if(1 == pdlink->clen)
    {
        free(pdlink->phead);
        pdlink->phead = NULL;
        pdlink->clen--;
    }
    else
    {
        Node *temp = pdlink->phead;
        pdlink->phead = temp->pnext;
        free(temp);
        pdlink->phead->ppre = NULL;
        pdlink->clen--;
    }
}

尾删

        核心逻辑:链表中的结点为1,直接释放空间,并且将phead置空,链表当前长度 pdlink->clen 减 1;结点为多个,使用while循环遍历链表,直到temp->pnextNULL,此时temp指向链表最后一个节点。将倒数第二个节点的pnext指针设为NULL,断开与尾节点的连接。释放尾节点temp占用的内存。将链表长度计数器pdlink->clen减1。

void DeleteTailDouLink(PdLink *pdlink)
{

    if(Isempty(pdlink))
    {
        printf("empty\n");
        return ;
    }
    Node *temp = pdlink->phead;
    if(1 == pdlink->clen)
    {
        free(temp);
        pdlink->phead = NULL;
        pdlink->clen--;
    }
    else
    {
        while(temp->pnext)
        {
            temp = temp->pnext;
        }
        temp->ppre->pnext = NULL;
        free(temp);
        pdlink->clen--;
        
    }
}

查找

        核心逻辑:优先检查链表状态,避免无效操作。同时验证节点存在性ptemp和名称匹配性strcmp,确保安全遍历。直接利用指针状态作为查找结果的标志,简化逻辑判断。

Node *FindNmaeofDouLink(PdLink *pdlink,char *name)
{
    if(Isempty(pdlink))
    {
        printf("empty\n");
        return NULL;
    }
    Node *ptemp = pdlink->phead;
    while(ptemp && strcmp(ptemp->data.name,name))
    {
        ptemp = ptemp->pnext;
    }
    if(ptemp)
    {
        return ptemp;
    }
    else
    {
        return NULL;
    }
}

修改

        核心逻辑:检查传入的双向链表 pdlink 是否为空。如果链表为空,打印 "empty" 并直接返回。如果链表不为空,调用 FindNmaeofDouLink 函数在链表中查找与 name 匹配的节点。该函数返回指向找到的节点的指针 temp。如果 temp 不为空(即找到匹配节点),将节点的 data.score 字段更新为 newsorce 值。

void ChangeSorce(PdLink *pdlink,char *name,int newsorce)
{
     if(Isempty(pdlink))
    {
        printf("empty\n");
        return ;
    }
    Node * temp = FindNmaeofDouLink(pdlink,name);
    if(temp)
    {
        temp->data.score = newsorce;
    }
    else
    {
        printf("not found\n");
    }    
}

删除链表

        核心逻辑:检查双向链表的头节点 phead 是否为空。如果头节点为空,说明链表没有存储任何数据节点,直接释放链表结构 pdlink 的内存。如果头节点不为空,说明链表中存在数据节点。通过循环检查当前链表长度 clen,只要长度不为零,就调用 DeleteTailDouLink 函数从链表尾部删除节点。这个过程会持续直到所有数据节点都被删除。最后,释放链表结构 pdlink 的内存。

void DestroyDouLink(PdLink *pdlink)
{
    if(pdlink->phead == NULL)
    {
        free(pdlink);
    }
    else
    {
        while(pdlink->clen)
        {
            DeleteTailDouLink(pdlink);
        }
        free(pdlink);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值