(C/C++)初识单向链表

15 篇文章 0 订阅
4 篇文章 0 订阅

第一次写博客,如果写得不好请谅解,欢迎大佬们一起交流讨论。
在我初学链表的时候,会觉得书上讲解十分抽象,理解到头炸,在通过做题的方式后,对链表又产生了新的认识和看法,使用链表的方式更加灵活了,通过这篇文章与大家分享一下单向链表的一些知识。
本文章主要讲单向链表:创建输出排序插入,- 删除清空


链表是一种重要的数据结构,它是动态地进行储存分配的一种结构,并且链表必须用指针变量才能实现(重点!)。

1. 创建

首先,建立动态链表,就是在程序运行中根据需求向系统申请储存空间,类似int *m = new int[n]的方式构造动态数组,概念是一种从无到有地建立新节点,并将各节点连接形成链表。

这篇文章以成员为id和成绩作为例子,通过C++实现。
环境:Code::Blocks_16.1 && VS2017

题目:
第1~n行每行都是两个整数,第一个是 id(唯一的),第二个是 score;第n+1行只有一个数字,0(n可能是0)。
这里的id输入是未排好序的整数
节点的声明:

struct Node {
       int id;
       int score;
       struct Node *next;	// 此处*next表示尾巴 
 }*list_head; // 设list_head,为全局变量的指针

接下来要讲如何实现建立动态链表,先放代码再继续讲解。

void scan()	//创建链表
{
    int id;
    Node *list_temp;		// 过渡指针,负责将各节点连接
    list_head = NULL;		// 作为头部,还没有输入,此时为 NULL
	list_temp = list_head;	// 将过渡指针指向头部,即从第一个节点开始
	cout <<  “请输入id和score:<< endl; 
    while(cin >> id, id)	// 判断输入id是否非0
    {
        Node *list_body = new Node();		
        // 申请空间,这里的括号可以删除,但为了增加程序的可读性,建议保留
        list_body->id = id;
        cin >> list_body->score;
        list_body->next = NULL;
		/* 以上是成员赋值,因为是单向链表的使用,所以next必须为NULL*/
        if(list_head == NULL)	
        // 如果新节点是第一个节点,则将这个节点连接到头部list_head,即指针 list_head 为链表的头地址
            list_head = list_body;
        else	// 此时不是第一个节点,使用过渡指针list_temp将节点与新节点连接
            list_temp->next = list_body;
        list_temp = list_body;	// 过渡指针移动至新节点
    }
}

假如把链表比作串烤串,那么一开始的链表就是一根竹签(list_head = NULL)

这里写图片描述

之后每串入一块肉(body),就是new了一个新节点,并且这串肉首尾相连,竹签从肉出去的地方就是它的尾部(next),当没有下一个肉串入时,next = NULL
(list_body_1->next = list_body_2; list_body_2->next = list_body_3; …; list_body_n-1->next = list_body_n; 注意!list_body_n->next = NULL

这里写图片描述

这里写图片描述

看上去将“肉”串在一起用编程实现很困难,其实只要通过指针list_temp就可以实现将各块“肉”首尾相连,这里我将指针list_temp称为过渡指针

指针list_temp是通过,将新节点的地址复制给next来实现节点连接,这块的知识我自己学都觉得很抽象,得通过图文结合来理解,建立起框架。

指针list_temp是不停变换的,每次连接好节点后,便会移动至新节点的位置,不改变其他变量,确保不会指针漂移

这里写图片描述

n个相连的body(节点)形成单向链表,list_head是第一个body的地址,每个body的next指向的是下一个body的地址,除了最后一个body指向的是NULL(因为后面已经没有body),其实就是串烤肉这么简单。

2. 输出

链表的输出,这部分比较简单。

代码部分:

/* 通过过渡指针 list_temp 从 head 位置到链表结尾,移动至每个节点来实现输出成员 */
void print_list()
{
    Node *list_temp = list_head;
    cout << "*** LISTING ***" << endl;
    while(list_temp != NULL) // 判断当前位置是否存在节点
    {
    	cout << "id:" << list_temp->id << '\t' << "score:" << list_temp->score << endl;
        list_temp = list_temp->next;
    }
    cout << "*** END OF FILE ***" << endl;
}

这里写图片描述

这里可以理解为吃烤串,不过正常的吃烤串是从竹签尖的那端开始吃,而这里的“吃烤串”是从竹签底端的肉(list_temphead)开始往上吃(即往list_temp的箭头方向移动)

3. 排序

对于刚入门的菜鸟,先了解使用冒泡法排序链表,感兴趣的还可以去了解快排归并排序

void cmp_list() //升序冒泡排序
{
    Node *list_temp = NULL; //控制外循环,指向需要排序的第一个节点
    Node *list_end = NULL;  //控制内循环,指向需要排序的最后一个节点
    list_temp = list_head;  //指向表头
    while(list_temp != list_end)
    {
        while(list_temp->next != list_end)
        {
            /*这里的数据交换根据实际情况,但原理相同*/
            if(list_temp->id > list_temp->next ->id )   //当前节点的id与下一个节点的id比较
            {
                //用整数cache_id和cache_score分别记录当前的位置的id和score
                int cache_id = list_temp->id;
                int cache_score = list_temp->score;
                //下面的代码是交换两个链表的数据
                list_temp->id = list_temp->next->id;
                list_temp->score = list_temp->next->score;
                list_temp->next->id = cache_id;
                list_temp->next->score = cache_score;
            }
            list_temp = list_temp->next ;
        }
        list_end = list_temp;   //将当前排序的最后一个节点向前挪动一个位置
        list_temp = list_head;  //重新指向表头
    }
}

只要能理解数组的冒泡法,相信也能理解链表的冒泡排序,因为原理是相同的。这段代码我已经优化了一些,把链表无节点和链表只有一个节点的情况也考虑到了。

4. 插入

往链表中插入数据,先贴代码:

void add_point()
{
    cout << "请输入新数据:" << endl;
    Node *list_temp = list_head;
    Node *new_body = new Node();
    /*成员赋值*/
    cin >> new_body->id >> new_body->score;
    new_body->next = NULL;

    /*if里的思想是:如果当前是空链表或新数据比第一个数小,则这样写
      else里的思想是:当前链表已经升序排序过,所以满足插入的条件是出现数据比现在的值大或全部id都小于新id
    */
    if(list_temp == NULL || new_body->id < list_temp->id)
    {
        new_body->next = list_temp;
        list_head = new_body;
    }
    else
    {
        while(list_temp->next != NULL && list_temp->id < new_body->id )
        {
            if(list_temp->next->id > new_body->id)
                break;
            list_temp = list_temp->next;
        }
        new_body->next = list_temp->next;
        list_temp->next = new_body;
    }
}

链表排序后的插入我认为不好写,这段代码是我经过不断优化的最终写法,也有更简单的方法,那就是在链表尾部添加新数据,接着再次调用排序(例如冒泡法)。
链表插入的简单思想就是插入作为第一个节点,还是不是作为第一个节点插入。事实上,用双向链表来做插入更方便。

5. 删除

链表节点的删除就几种情况,首和尾和非首尾,像我用的这个链表只要考虑首和非首就可以。

void delete_point()
{
    int id;
    cout << "请输入要删除的id:" << endl;
    cin >> id;
    Node *list_temp = list_head, *cache = NULL;
    if(list_head != NULL)
    {
        if(id == list_temp->id)  //删除第一个节点
        {
            cache = list_temp->next;
            delete (list_temp);
            list_temp = NULL;
            list_head = cache;
        }
        else    //删除其他节点
        {
            while(list_temp->next != NULL && id != list_temp->id)
            {
                cache = list_temp;
                list_temp = list_temp->next;
            }
            if(id == list_temp->id)
            {
                cache->next = list_temp->next;
                delete (list_temp);
                list_temp = NULL;
            }
        }
    }
}

链表的节点删除是通过搜索来删除数据,主要是要考虑 当前符合的数据是链表的第一个节点还是其他节点,如果是第一个节点的话,那么就必须得修改list_head的位置。

6. 清空

void delete_all()
{
    if(list_head != NULL)
    {
        Node *list_temp = NULL, *cache = NULL;
        list_temp = list_head;
        while(list_temp != NULL)
        {
            cache= list_temp->next;
            delete(list_temp);
            list_temp = NULL;
            list_temp = cache;
        }
    }
    cout << "链表已清空:" << endl;
}

链表的清空非常简单,并且链表的清空是必须的!!每次删除节点,一定要防止内存泄漏,虽然可能有内存回收机制,但是建议令该节点等于NULL,可以防止出现小问题。

7. 完整代码

#include <iostream>
using namespace std;
struct Node
{
    int id;
    int score;
    struct Node *next;	//此处*next表示尾巴
}*list_head;//设list_head,为全局变量的指针


void scan_list();    //链表的输入
void print_list();    //链表的输出
void cmp_list();     //链表的排序
void add_point();     //链表节点的添加
void delete_point();     //链表节点的删除
void delete_all();      //链表的清空

int main()
{
    scan_list();
    cout << "链表的创建:" << endl;
    print_list();
    cmp_list();
    cout << "链表的排序:" << endl;
    print_list();
    add_point();
    cout << "链表的添加:" << endl;
    print_list();
    delete_point();
    cout << "链表的删除:" << endl;
    print_list();
    delete_all();
    return 0;
}

void scan_list()
{
    int id;
    Node *list_temp = NULL;		//过渡指针,负责将各节点连接
    list_head = NULL;		//作为头部,还没有输入,此时为NULL
    list_temp = list_head;	//将过渡指针指向头部,即从第一个节点开始
    cout << "请输入id和score:" << endl;
    while(cin >> id,id)	//判断输入id是否非0
    {
        Node *list_body = new Node();		//申请空间,这里的括号可以删除,但为了增加程序的可读性,建议保留
        list_body->id = id;
        cin >> list_body->score;
        list_body->next = NULL;
        /*以上是成员赋值,因为是单向链表的使用,所以next必须为NULL*/
        if(list_head == NULL)	//如果新节点是第一个节点,则将这个节点连接到头部list_head,即指针list_head为链表的头地址
            list_head = list_body;
        else	//此时不是第一个节点,使用过渡指针list_temp将节点与新节点连接
            list_temp->next = list_body;
        list_temp = list_body;	//过渡指针移动至新节点
    }
}

void print_list()
{
    Node *list_temp = list_head;
    cout << "*** LISTING ***" << endl;
    while(list_temp != NULL)	//判断当前位置是否存在节点
    {
        cout << "id:" << list_temp->id << '\t' << "score:" << list_temp->score << endl;
        list_temp = list_temp->next;
    }
    cout << "*** END OF FILE ***" << endl;
}

void cmp_list() //升序冒泡排序
{
    Node *list_temp = NULL; //控制外循环,指向需要排序的第一个节点
    Node *list_end = NULL;  //控制内循环,指向需要排序的最后一个节点
    list_temp = list_head;  //指向表头
    while(list_temp != list_end)
    {
        while(list_temp->next != list_end)
        {
            /*这里的数据交换根据实际情况,但原理相同*/
            if(list_temp->id > list_temp->next ->id )   //当前节点的id与下一个节点的id比较
            {
                //用整数cache_id和cache_score分别记录当前的位置的id和score
                int cache_id = list_temp->id;
                int cache_score = list_temp->score;
                //下面的代码是交换两个链表的数据
                list_temp->id = list_temp->next->id;
                list_temp->score = list_temp->next->score;
                list_temp->next->id = cache_id;
                list_temp->next->score = cache_score;
            }
            list_temp = list_temp->next ;
        }
        list_end = list_temp;   //将当前排序的最后一个节点向前挪动一个位置
        list_temp = list_head;  //重新指向表头
    }
}

void add_point()
{
    cout << "请输入新数据:" << endl;
    Node *list_temp = list_head;
    Node *new_body = new Node();
    /*成员赋值*/
    cin >> new_body->id >> new_body->score;
    new_body->next = NULL;

    /*if里的思想是:如果当前是空链表或新数据比第一个数小,则这样写
      else里的思想是:当前链表已经升序排序过,所以满足插入的条件是出现数据比现在的值大或全部id都小于新id
    */
    if(list_temp == NULL || new_body->id < list_temp->id)
    {
        new_body->next = list_temp;
        list_head = new_body;
    }
    else
    {
        while(list_temp->next != NULL && list_temp->id < new_body->id )
        {
            if(list_temp->next->id > new_body->id)
                break;
            list_temp = list_temp->next;
        }
        new_body->next = list_temp->next;
        list_temp->next = new_body;
    }
}

void delete_point()
{
    int id;
    cout << "请输入要删除的id:" << endl;
    cin >> id;
    Node *list_temp = list_head, *cache = NULL;
    if(list_head != NULL)
    {
        if(id == list_temp->id)  //删除第一个节点
        {
            cache = list_temp->next;
            delete (list_temp);
            list_temp = NULL;
            list_head = cache;
        }
        else    //删除其他节点
        {
            while(list_temp->next != NULL && id != list_temp->id)
            {
                cache = list_temp;
                list_temp = list_temp->next;
            }
            if(id == list_temp->id)
            {
                cache->next = list_temp->next;
                delete (list_temp);
                list_temp = NULL;
            }
        }
    }
}

void delete_all()
{
    if(list_head != NULL)
    {
        Node *list_temp = NULL, *cache = NULL;
        list_temp = list_head;
        while(list_temp != NULL)
        {
            cache= list_temp->next;
            delete(list_temp);
            list_temp = NULL;
            list_temp = cache;
        }
    }
    cout << "链表已清空:" << endl;
}

我希望我的这篇博客对链表初学者可以为提供思路,注意,链表是C++/C里很重要的一个部分,还请大家看我的代码不仅仅是复制黏贴那么简单。我的代码或许还存在一些bug或可以完善的地方,欢迎大佬们指点与讨论。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值