单链表

( 1 )时刻谨记:链表就是用来解决数组的大小不能动态扩展的问题,所以链表其实就是当数组用的。直白点:链表能完成的任务用数组也能完成,数组能完成的任务用链表也能完成。但是灵活性不一样。
( 2 )简单说:链表就是用来存储数据的。链表用来存数据相对于数组来说优点就是灵活性,需要多少个动态分配多少个,不占用额外的内存。数组的优势是使用简单(简单粗暴)。

// 构建一个链表的节点
struct  node
{
     int  data;                 // 有效数据
     struct  node  * pNext;         // 指向下一个节点的指针
};

( 1 )链表的内存要求比较灵活,不能用栈,也不能用data数据段。只能用堆内存。
( 2 )使用堆内存来创建一个链表节点的步骤: 1 、申请堆内存,大小为一个节点的大小(检查申请结果是否正确); 2 、清理申请到的堆内存; 3 、把申请到的堆内存当作一个新节点; 4 、填充你哦个新节点的有效数据和指针区域。

// 定义头指针
     struct  node  * pHeader  =  NULL;
    
     /********************************************************************/
     // 每创建一个新的节点,把这个新的节点和它前一个节点关联起来
     // 创建一个链表节点
     struct  node  * =  ( struct  node  * )malloc( sizeof ( struct  node));
     if  (NULL  ==  p)
    {
        printf( "malloc error.\n" );
         return   - 1 ;
    }
     // 清理申请到的堆内存
    bzero(p,  sizeof ( struct  node));    // 也可以用memset:memset(p,0,sizeof(struct node));
     // 填充节点
    p - > data  =   1 ;
    p - > pNext  =  NULL;             // 将来要指向下一个节点的首地址
                                 // 实际操作时将下一个节点malloc返回的指针赋值给这个
                                
    pHeader  =  p;     // 将本节点和它前面的头指针关联起来                     
     /********************************************************************/
    
    
     /********************************************************************/
     // 每创建一个新的节点,把这个新的节点和它前一个节点关联起来
     // 创建一个链表节点
     struct  node  * p1  =  ( struct  node  * )malloc( sizeof ( struct  node));
     if  (NULL  ==  p1)
    {
        printf( "malloc error.\n" );
         return   - 1 ;
    }
     // 清理申请到的堆内存
    bzero(p1,  sizeof ( struct  node));
     // 填充节点
    p1 - > data  =   2 ;
    p1 - > pNext  =  NULL;             // 将来要指向下一个节点的首地址
                                 // 实际操作时将下一个节点malloc返回的指针赋值给这个
                                
    p - > pNext  =  p1;     // 将本节点和它前面的头指针关联起来                     
     /********************************************************************/
     /********************************************************************/
     // 每创建一个新的节点,把这个新的节点和它前一个节点关联起来
     // 创建一个链表节点
     struct  node  * p2  =  ( struct  node  * )malloc( sizeof ( struct  node));
     if  (NULL  ==  p2)
    {
        printf( "malloc error.\n" );
         return   - 1 ;
    }
     // 清理申请到的堆内存
    bzero(p2,  sizeof ( struct  node));
     // 填充节点
    p2 - > data  =   3 ;
    p1 - > pNext  =  p2;             // 将来要指向下一个节点的首地址
                                 // 实际操作时将下一个节点malloc返回的指针赋值给这个         

     // 访问链表第1个节点的有效数据
    printf( "node1 data: %d.\n" , pHeader - > data);    
    printf( "p->data: %d.\n" , p - > data);             // pHeader->data等同于p->data
    
     // 访问链表第2个节点的有效数据
    printf( "node2 data: %d.\n" , pHeader - > pNext - > data);    
    printf( "p1->data: %d.\n" , p1 - > data);    
     // pHeader->pNext->data等同于p1->data
    
     // 访问链表第3个节点的有效数据
    printf( "node3 data: %d.\n" , pHeader - > pNext - > pNext - > data);    
    printf( "p2->data: %d.\n" , p2 - > data);            
     // pHeader->pNext->pNext->data等同于p2->data                         

                                

将创建节点的代码封装成一个函数

( 1 )封装时的关键点就是函数的接口(函数参数和返回值)的设计                                
struct  node  *  create_node( int  data)
{
     struct  node  * =  ( struct  node  * )malloc( sizeof ( struct  node));
     if  (NULL  ==  p)
    {
        printf( "malloc error.\n" );
         return  NULL;
    }
     // 清理申请到的堆内存
    bzero(p,  sizeof ( struct  node));
     // 填充节点
    p - > data  =  data;
    p - > pNext  =  NULL;    
    
     return  p;
}                                                        
    pHeader  =  create_node( 1 );    
     // 将本节点和它前面的头指针关联起来                     
    pHeader - > pNext  =  create_node( 432 );        
     // 将本节点和它前面的头指针关联起来                     
    pHeader - > pNext - > pNext  =  create_node( 123 );                
     // 将来要指向下一个节点的首地址                                 
                                
从链表尾部插入新节点
// 思路:由头指针向后遍历,直到走到原来的最后一个节点。原来最后一个节点里面的pNext是NULL,现在我们只要将它改成new就可以了。添加了之后新节点就变成了最后一个。
void  insert_tail( struct  node  * pH,  struct  node  * new )
{
     // 分两步来完成插入
     // 第一步,先找到链表中最后一个节点
     struct  node  * =  pH;
     while  (NULL  !=  p - > pNext)
    {
        p  =  p - > pNext;                 // 往后走一个节点
    }
    
     // 第二步,将新节点插入到最后一个节点尾部
    p - > pNext  =   new ;
}

// 计算添加了新的节点后总共有多少个节点,然后把这个数写进头节点中。
//由上一个函数修改所得
void  insert_tail( struct  node  * pH,  struct  node  * new )
{
     int  cnt  =   0 ;
     // 分两步来完成插入
     // 第一步,先找到链表中最后一个节点
     struct  node  * =  pH;
     while  (NULL  !=  p - > pNext)
    {
        p  =  p - > pNext;                 // 往后走一个节点
        cnt ++ ;
    }
     // 第二步,将新节点插入到最后一个节点尾部
    p - > pNext  =   new ;
    pH - > data  =  cnt  +   1 ;
}        
                    
int  main( void )
{
     // 定义头指针
     //struct node *pHeader = NULL;            // 这样直接insert_tail会段错误。
     struct  node  * pHeader  =  create_node( 0 );
    
    insert_tail(pHeader, create_node( 1 ));
    insert_tail(pHeader, create_node( 2 ));
    insert_tail(pHeader, create_node( 3 ));    
     return   0 ;
}                            
                                
4 . 9 . 5 .从链表头部插入新节点        
// 思路:
void  insert_head( struct  node  * pH,  struct  node  * new )
{
     // 第1步: 新节点的next指向原来的第一个节点
     new - > pNext  =  pH - > pNext;
    
     // 第2步: 头节点的next指向新节点的地址
    pH - > pNext  =   new ;
    
     // 第3步: 头节点中的计数要加1
    pH - > data  +=   1 ;
}

int  main( void )
{
     // 定义头指针
     //struct node *pHeader = NULL;            // 这样直接insert_tail会段错误。
     struct  node  * pHeader  =  create_node( 0 );
    
    insert_head(pHeader, create_node( 1 ));
    insert_tail(pHeader, create_node( 2 ));
    insert_head(pHeader, create_node( 3 ));

    printf( "beader node data: %d.\n" , pHeader - > data);    

     // 访问链表第1个节点的有效数据
    printf( "node1 data: %d.\n" , pHeader - > pNext - > data);    
    
     // 访问链表第2个节点的有效数据
    printf( "node2 data: %d.\n" , pHeader - > pNext - > pNext - > data);    
    
     // 访问链表第3个节点的有效数据
    printf( "node3 data: %d.\n" , pHeader - > pNext - > pNext - > pNext - > data);    
    
     return   0 ;
}

4 . 9 . 6 .单链表的算法之遍历节点
// 遍历单链表,pH为指向单链表的头指针,遍历的节点数据打印出来
void  bianli( struct  node * pH)
{
     //pH->data                // 头节点数据,不是链表的常规数据,不要算进去了
     //struct node *p = pH;        // 错误,因为头指针后面是头节点
     struct  node  * =  pH - > pNext;     // p直接走到第一个节点
    printf( "-----------开始遍历-----------\n" );
     while  (NULL  !=  p - > pNext)         // 是不是最后一个节点
    {
        printf( "node data: %d.\n" , p - > data);
        p  =  p - > pNext;                 // 走到下一个节点,也就是循环增量
    }
    printf( "node data: %d.\n" , p - > data);
    printf( "-------------完了-------------\n" );
}
// 1、思考下为什么这样能解决问题;2、思考下设计链表时为什么要设计头节点
void  bianli2( struct  node * pH)
{
     //pH->data                // 头节点数据,不是链表的常规数据,不要算进去了
     struct  node  * =  pH;         // 头指针后面是头节点

    printf( "-----------开始遍历-----------\n" );
     while  (NULL  !=  p - > pNext)         // 是不是最后一个节点
    {
        p  =  p - > pNext;                 // 走到下一个节点,也就是循环增量
        printf( "node data: %d.\n" , p - > data);
    }

    printf( "-------------完了-------------\n" );
}

单链表的算法之删除节点
// 从链表pH中删除节点,待删除的节点的特征是数据区等于data
// 返回值:当找到并且成功删除了节点则返回0,当未找到节点时返回-1
int  delete_node( struct  node * pH,  int  data)
{
     // 找到这个待删除的节点,通过遍历链表来查找
     struct  node  * =  pH;             // 用来指向当前节点
     struct  node  * pPrev  =  NULL;         // 用来指向当前节点的前一个节点

     while  (NULL  !=  p - > pNext)         // 是不是最后一个节点
    {
        pPrev  =  p;                     // 在p走向下一个节点前先将其保存
        p  =  p - > pNext;                 // 走到下一个节点,也就是循环增量
         // 判断这个节点是不是我们要找的那个节点
         if  (p - > data  ==  data)
        {
             // 找到了节点,处理这个节点
             // 分为2种情况,一个是找到的是普通节点,另一个是找到的是尾节点
             // 删除节点的困难点在于:通过链表的遍历依次访问各个节点,找到这个节点
             // 后p指向了这个节点,但是要删除这个节点关键要操作前一个节点,但是这
             // 时候已经没有指针指向前一个节点了,所以没法操作。解决方案就是增加
             // 一个指针指向当前节点的前一个节点
             if  (NULL  ==  p - > pNext)
            {
                 // 尾节点
                pPrev - > pNext  =  NULL;         // 原来尾节点的前一个节点变成新尾节点
                free(p);                     // 释放原来的尾节点的内存
            }
             else
            {
                 // 普通节点
                pPrev - > pNext  =  p - > pNext;     // 要删除的节点的前一个节点和它的后一个节点相连,这样就把要删除的节点给摘出来了
                free(p);
            }
             // 处理完成之后退出程序
             return   0 ;
        }
    }
     // 到这里还没找到,说明链表中没有我们想要的节点
    printf( "没找到这个节点.\n" );
     return   - 1 ;
}

delete_node(pHeader,  12 );

4 . 9 . 8 . 2 、单链表逆序算法分析
//思路:首先遍历原链表,然后将原链表中的头指针和头节点作为新链表的头指针和头节点,原链表中的有效节点挨个依次取出来,采用头插入的方法插入新链表中即可。
// 将pH指向的链表逆序
void  reverse_linkedlist( struct  node  * pH)
{
     struct  node  * =  pH - > pNext;         // pH指向头节点,p指向第1个有效节点
     struct  node  * pBack;                 // 保存当前节点的后一个节点地址
    
     // 当链表没有有效节点或者只有一个有效节点时,逆序不用做任何操作
     if  ((NULL  == p)  ||  (NULL  ==  p - > pNext))
         return ;
    
     // 当链表有2个及2个以上节点时才需要真正进行逆序操作
     while  (NULL  !=  p - > pNext)         // 是不是最后一个节点
    {
         // 原链表中第一个有效节点将是逆序后新链表的尾节点,尾节点的pNext指向NULL
        pBack  =  p - > pNext;             // 保存p节点后面一个节点地址
         if  (p  ==  pH - > pNext)
        {
             // 原链表第一个有效节点
            p - > pNext  =  NULL;
        }
         else
        {
             // 原链表的非第1个有效节点
            p - > pNext  =  pH - > pNext;
        }
        pH - > pNext  =  p;
        
         //p = p->pNext;        // 这样已经不行了,因为p->pNext已经被改过了
        p  =  pBack;             // 走到下一个节点
    }
     // 循环结束后,最后一个节点仍然缺失
    insert_head(pH, p);
}











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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值