C和指针(学习笔记)_第12章 使用结构和指针

1.链表

       链表是包含数据的独立数据结构(通常称为节点)的集合。链表中的每个节点通过链或指针连接在一起,程序通过指针访问链表中的节点,通常节点是动态分配的,但有时也能看到由节点数组构建的链表。

2.单链表

      在单链表中,每个节点包含一个指向链表下一节点的指针,链表最后一个结点的指针字段的值为NULL,提示链表后面不再有其它结点。链表的起始位置可以使用一个根指针,根指针指向链表的第一个节点,注意根结点只是一个指针,它不含任何数据。

type struct NODE{
     struct NODE  *link;
     int          value;
}Node;

程序始终用链(指针)从一个结点移动到另一个结点。单链表可以通过链表从开始位置遍历链表直到结束位置,但链表无法从相反的方向进行遍历,

在单链表中插入:有序链表的插入,基本思想移位法

注意:头部和尾部的插入

      头部插入稍好的解决方法是把一个指向root的指针作为参数传递给函数,然后使用间接访问,函数不仅可以获得root的值,也可以向它存储一个新的指针值。

result=sll_insert(&root,12);


int sll_inset(Node **rootp,int new_value){
    Node *current;
    Node *previous;
    Node *new;

    //得到指向第一个节点的指针
    current = *rootp;
    previous = NULL;
   
    //寻找正确位置,current !=NULL是为了防止循环越过尾部
    while(current !=NULL & current->value<new_value){
        preview = current;
        current = current->link;
    }

    //为新节点分配内存,并把新值存储到新节点,如果内存分配失败,返回fail
    new = (Node *) malloc(sizeof(Node));
    if(new ==NULL)
        return FALSE;
    new->value=new_value;

    //把新节点插入到链表中,并返回true
    new->link=current;
    if(preview ==NULL)
        *root =new; //插入头结点
    else
        previous->link=new; //中间或尾部
    return true;
}

优化插入函数:消除头部特殊情况的关键在于:必须认识到链表中的每个节点都有一个指向它的指针,对于第一个节点,这是指向根结点的指针,,对于其他节点,这个指针是前一个结点的link字段,重点在于每个节点都有一个指针指向它。

root指针并不指向节点本身,而是指向节点内部的link字段,这是简化插入函数的关键,但必须能够取得当前节点的link字段的地址,在C中可通过&current->link来达到目的。

result=sll_insert(&root,12);


int sll_inset(register Node **linkp,int new_value){
    register Node *current;
    register Node *new;

    //寻找正确位置,current !=NULL是为了防止循环越过尾部
    while((current=*linkp) !=NULL && current->value<new_value)
       linkp = &current->link;

    //为新节点分配内存,并把新值存储到新节点,如果内存分配失败,返回fail
    new = (Node *) malloc(sizeof(Node));
    if(new ==NULL)
        return FALSE;
    new->value=new_value;

    //把新节点插入到链表中,并返回true
    new->link=current;
    *linkp = new;
    return true;
}

单链表的增删改查,是需要后续学习的重要知识.

3.双向链表

       单链表的替代方案就是双链表。在一个双链表中,每个节点包含两个指针--指向前一个节点的指针和指向后一个节点的指针。如此就可以双向遍历双链表。

typedef struct NODE{
    struct NODE *fwd;
    struct NODE *bwd;
    int         value;
}Node;

根节点的fwd字段指向链表的第一个节点,根节点的bwd字段指向链表的最后一个节点;若链表为空,这两个字段都为空NULL;链表第一个节点的bwd字段和最后一个节点的fwd字段都为NULL。

在双向链表中插入:当把一个节点插入到一个链表时,可能出现4中情况:

       1)新值可能必须插入到链表的中间位置;

       2)新值可能必须插入到链表的起始位置;

       3)新增可能必须插入到链表的结束位置;

       4)新增可能必须既插入到链表的起始位置,又插入到链表结束位置(即原链表为NULL);

int dll_insert(Node **rootp,int value){
    Node *this;
    Node *next;
    Node *newnode;

    //查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
    //this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
    for(this=rootp;(next=this->fwd)!=NULL;this=next){
        if(next->value==value)
            return 0;
        if(next->value>value)
            break;
    }
    newnode=(Node *)malloc(sizeof(Node));
    if(newnode==NULL)
        return -1;
    newnode->value=value;

    //把新值添加到链表中
    if(next!=NULL){
    //情况1或2:并非位于尾部
        if(this!=rootp){  //情况1:并非位于链表起始位置
            newnode->fwd=next;
            this->fwd=newnode;
            newnode->bwd=this;
            next->bwd=newnode;
        }else{//情况2:位于链表起始位置
            newnode->fwd=next;
            rootp->fwd=newnode;
            newnode->bwd=NULL;
            next->bwd=newnode; 
        }
    }else{
   //情况3或4:位于链表尾部
        if(this!=rootp){  //情况3:并非位于链表起始位置
            newnode->fwd=NULL;
            this->fwd=newnode;
            newnode->bwd=this;
            next->bwd=newnode;
        }else{//情况4:位于链表起始位置
            newnode->fwd=NULL;
            rootp->fwd=newnode;
            newnode->bwd=NULL;
            next->bwd=newnode; 
        }
    }
}

注意,在决定新值是否应该实际插入到链表之前,并不为它分配内存,如果事先分配内存,如果发现新增值原先已经存在于链表中,就有可能存在内存泄露。

第二版优化

int dll_insert(Node **rootp,int value){
    Node *this;
    Node *next;
    Node *newnode;

    //查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
    //this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
    for(this=rootp;(next=this->fwd)!=NULL;this=next){
        if(next->value==value)
            return 0;
        if(next->value>value)
            break;
    }
    newnode=(Node *)malloc(sizeof(Node));
    if(newnode==NULL)
        return -1;
    newnode->value=value;

    //把新值添加到链表中
    if(next!=NULL){
    //情况1或2:并非位于尾部
        newnode->fwd=next;
        if(this!=rootp){  //情况1:并非位于链表起始位置
            this->fwd=newnode;
            newnode->bwd=this;
        }else{//情况2:位于链表起始位置
            rootp->fwd=newnode;
            newnode->bwd=NULL;
        }
        next->bwd=newnode;
    }else{
   //情况3或4:位于链表尾部
        newnode->fwd=NULL;
        if(this!=rootp){  //情况3:并非位于链表起始位置
            this->fwd=newnode;
            newnode->bwd=this;
        }else{//情况4:位于链表起始位置
            rootp->fwd=newnode;
            newnode->bwd=NULL;
        }
        next->bwd=newnode; 
    }
}
 

进一步提炼

int dll_insert(Node **rootp,int value){
    Node *this;
    Node *next;
    Node *newnode;

    //查看value是否已存在与链表中,如果是就返回,否则为新值创建一个新节点(newnode指向它)
    //this将指向应该在新节点之前的那个节点,next将指向应该在新节点之后的那个节点
    for(this=rootp;(next=this->fwd)!=NULL;this=next){
        if(next->value==value)
            return 0;
        if(next->value>value)
            break;
    }
    newnode=(Node *)malloc(sizeof(Node));
    if(newnode==NULL)
        return -1;
    newnode->value=value;

    //把新值添加到链表中
    newnode->fwd=next;
    this->fwd=newnode;

    if(this!=rootp)
        newnode->bwd=this;
    else
        newnode->bwd=NULL;

    if(next!=NULL)
        next->bwd=this;
    else
        rootp->bwd=newnode;

    retun 1;
    }
}

双向链表的增删改查,是需要后续学习的重要知识.

4.总结

       单链表是一种使用指针来存储值的数据结构,节点在创建时时采用动态分配内存的方式,单链表只能以一个方向进行遍历。

       双链表每个节点包含两个link字段:一个指向链表的下一节点,另一指向链表上一节点,双链表有两个根指针,分别指向第一个节点和最后一个节点,双链表可以双向遍历,双链表的插入必须修改4个指针。

5,警告总结

     1)落在链表尾部的后面;

     2)使用指针时应格外小心,因为C并没有对它们的使用提供安全网;

     3)从if语句中提炼语句可能会改变测试结果;

6.编程提示总结

     1)消除特殊情况使用代码更易于维护;

     2)通过提炼语句消除if语句中的重复语句;

     3)不要仅仅根据代码的大小评估它的质量;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值