第二十三篇,数据结构(起始篇)—单向链表。

一、数据结构学习大纲。
1、数组与链表之间存储区别。
2、链表模型、使用场景。
3、链表种类?
传统链表   --> 单向链表、单向循环链表、双向链表、双向循环链表。
非传统链表 --> 内核链表
4、栈/队列
5、二叉树

无论学习什么数据结构,其实都是在学习该结构的增删改查。

二、回顾学习过一些数据结构  -- 增删改查。
变量: int x = 0;
增:x = 10;
删:x = 0;
改:x = 20
查:printf("%d\n",x);

数组:int x[3] = {0};
增:x[0] = 1 x[1] = 2 x[2] = 3
删:bzero(x,sizeof(x));
改:x[0] = 4 x[1] = 5 x[2] = 6
查:printf("x[0] = %d\n",x[0]);
    printf("x[1] = %d\n",x[1]);
    printf("x[2] = %d\n",x[2]);

三、数组与链表之间存储区别。
1、什么是数组?
1)数组空间在哪里申请?
如果数组是局部变量,那么该数组的空间就会在栈区申请。
如果数组是全局变量,那么该数组的空间就会在数据段申请。

2)数组的空间在内存中是连续的吗?
是。例如:int x[3]  -> 在内存空间上连续申请12个字节,然后使用变量x间接访问这片内存空间。

3)数组在内存特点?
在内存空间中是连续的,所以只需要通过数组名+下标方式就可以访问任意一个成员。
因为下标其实就是数组首元素的地址往上偏移的单位数。

4)数组本身空间特点?
1>. 在定义数组时,必须声明数组的大小。  --> 限制了数组空间操作的大小。
2>. 在给数组元素赋值时,必须要检查好空间大小,不然容易越界访问一些未知区域。

2、什么是链表?
1)链表的空间是在哪里申请?
链表空间都是在堆区申请。  
通过malloc函数申请空间,通过free函数释放空间。

2)链表的空间在内存是连续的吗?
不是连续,链表其实就是把那些零碎的空间组织起来,形成一个结构。

3)链表在内存特点?
由于链表每一个节点空间不是连续的,所以不能通过下标偏移来访问下一个成员。
每一个节点除了存储自身的数据(数据域)之外,还会存储下一个成员的地址(指针域),某一个成员就是通过本身的指针域中的地址来访问下一个成员的。

4)链表本身空间特点?
链表没有固定的长度,只要运行内存足够大,那么链表就可以无限延伸。
如果想在链表中新增新的节点,只需要申请新的空间,然后让最后那个节点存储新节点的地址就可以。
  

四、链表的模型。
1、链表的组成?
一个链表中会有非常多的成员组成,而每一个成员,我们都称之为节点。
头节点:排在第一个位置的节点称之为头节点。
头节点的特点: 数据域无效、指针域有效。

数据域无效  --> 数据域空间是存在的,但是不存储数据。
指针域有效  --> 指针域空间是存在的,也存储数据。

其他节点: 数据域有效,指针域也有效。

2、一个节点由什么组成?
由两部分组成:
数据域: 就是节点中存储自身数据的空间。        --> char/short/int/long/float/double/数组/指针/结构体
指针域: 就是节点中存储下一个节点地址的空间。  --> 指针

结论:数据域与指针域类型是不一样的,所以要描述一个节点,需要通过一个结构体来描述。

3、一个节点既有数据域,也有指针域,该如何描述?
struct list_node{
    /* 数据域 */

    /* 指针域 */
};

   例子1: 假设链表中每一个节点都是存储一个int类型的数据,那么节点如何表示?

struct list_node{
    int data;  //数据域
    struct list_node *next; //指针域
};


   例子2: 假设链表每一个节点都是存储一张图片的名字/路径,那么节点如何表示?

struct list_node{
    char picname[50];
    struct list_node *next; //指针域
}

   
   例子3: 假设链表中每一个节点都是存储一个小孩,那么节点如何表示?

struct list_node{
    char name[50];
    char passwd[50];
    int age;
    char tel[50];
    int state;
    time_t start_login_time;
    float total_login_time;
    time_t start_study_time;
    float total_study_time;
    time_t start_play_time;
    float total_play_time;
    int level;
    int register_flags;
    int login_flags;
    char leave_say[50];
    struct safe qa;
    
    struct list_node *next; //指针域
};

结论:链表中数据域存什么,取决于你,你想存什么,就存什么。
      链表指针域固定这么写的。


五、举例子。
假设有一条链表,每一个节点都是存储int类型的数据,分别依次是10,20,30,请把链表写出来。

1、设计链表中节点
由于每一个节点都是存放一个int类型的数据,所以节点应该表示如下:
struct list_node{
    int data;
    struct list_node *next;
};

2、初始化头节点。
1>. 为头节点申请空间。
2>. 为头节点空间赋值。

3、插入节点
1>. 为新节点申请空间
2>. 为新节点赋值
3>. 寻找最后一个节点
4>. 让最后一个节点指向新节点

----------------------------------------------------------------------------------------

#include <stdlib.h>
#include <stdio.h>

struct list_node{
    int data;   //数据域
    struct list_node *next; //指针域    
};

struct list_node *init_list_head()
{
    //1. 为头节点申请空间
    struct list_node *head = malloc(sizeof(struct list_node));
    if(head == NULL)
        printf("malloc head error!\n");
    
    //2. 为头节点空间赋值
    head->next = NULL;
    
    //3. 将头节点的地址返回到主函数,给别的函数使用。
    return head;
}

void list_add_tail(struct list_node *head,int num)
{
    //1. 为新节点申请空间
    struct list_node *new = malloc(sizeof(struct list_node));
    if(new == NULL)
        printf("malloc new error!\n");
    
    //2. 为新节点的数据域与指针域赋值
    new->data = num;
    new->next = NULL;
    
    //3. 寻找链表中最后一个节点
    struct list_node *p = NULL;
    for( p=head ; p->next!=NULL ; p=p->next);
    
    //从循环中出来,p->next一定是NULL
    //说明当前p指向的节点就是链表中最后一个节点
    
    //4. 让最后一个节点的指针域存储着新节点的地址就可以。
    p->next = new;
    
    return;
}

int main(int argc,char *argv[])
{
    //1. 初始化头节点
    struct list_node *head = NULL;
    head = init_list_head();
    
    //2. 插入节点到链表末尾
    list_add_tail(head,10);
    list_add_tail(head,20);
    list_add_tail(head,30);
    list_add_tail(head,40);
    list_add_tail(head,50);
    
    return 0;
}


-----------------------------------------------------------------------


   练习1: 完成 show_list_node(???) 函数接口

void show_list_node(struct list_node *head)
{
    struct list_node *p = NULL;
    for(p=head->next;p!=NULL;p=p->next)
    {
        printf("p->data:%d\n",p->data);
    }
    
    return;
}

  练习2: 完成链表 list_add_head() 头插功能。

list_add_tail()  -> 尾插:在链表的末尾新增一个节点。
list_add_head()  -> 头插:在链表头节点之后插入新节点。


void list_add_head(struct list_node *head,int num)
{
    //1. 为新节点申请空间
    struct list_node *new = malloc(sizeof(struct list_node));
    if(new == NULL)
        printf("malloc new error!\n");
    
    //2. 为新节点赋值。
    new->data = num;
    new->next = head->next;
    head->next = new;
    
    return;
}

  练习3: 完成链表 delete_list_node()  删除功能。

----------------------------最终代码--------------------------
#include <stdlib.h>
#include <stdio.h>

struct list_node{
    int data;   //数据域
    struct list_node *next; //指针域    
};

struct list_node *init_list_head()
{
    //1. 为头节点申请空间
    struct list_node *head = malloc(sizeof(struct list_node));
    if(head == NULL)
        printf("malloc head error!\n");
    
    //2. 为头节点空间赋值
    head->next = NULL;
    
    //3. 将头节点的地址返回到主函数,给别的函数使用。
    return head;
}

void list_add_tail(struct list_node *head,int num)
{
    //1. 为新节点申请空间
    struct list_node *new = malloc(sizeof(struct list_node));
    if(new == NULL)
        printf("malloc new error!\n");
    
    //2. 为新节点的数据域与指针域赋值
    new->data = num;
    new->next = NULL;
    
    //3. 寻找链表中最后一个节点
    struct list_node *p = NULL;
    for( p=head ; p->next!=NULL ; p=p->next);
    
    //从循环中出来,p->next一定是NULL
    //说明当前p指向的节点就是链表中最后一个节点
    
    //4. 让最后一个节点的指针域存储着新节点的地址就可以。
    p->next = new;
    
    return;
}

void show_list_node(struct list_node *head)
{
    struct list_node *p = NULL;
    for(p=head->next;p!=NULL;p=p->next)
    {
        printf("p->data:%d\n",p->data);
    }
    
    return;
}

void list_add_head(struct list_node *head,int num)
{
    //1. 为新节点申请空间
    struct list_node *new = malloc(sizeof(struct list_node));
    if(new == NULL)
        printf("malloc new error!\n");
    
    //2. 为新节点赋值。
    new->data = num;
    new->next = head->next;
    head->next = new;
    
    return;
}

int search_list_node(struct list_node *head,int num)
{
    struct list_node *p = NULL;
    for(p=head->next;p!=NULL;p=p->next)
    {
        if(p->data == num)
        {
            printf("search node:%d\n",p->data);
            return 0;
        }
    }
    return -1;
}

int delete_list_node(struct list_node *head,int num)
{
    struct list_node *p = NULL;
    struct list_node *q = NULL;
    
    for(q=head,p=head->next;p!=NULL;q=p,p=p->next)
    {
        if(p->data == num)
        {
            q->next = p->next;
            free(p);
            return 0;
        }
    }
    
    return -1;
}

void delete_list(struct list_node *head)
{
    struct list_node *p = NULL;
    struct list_node *q = NULL;
    for(p=q=head;p!=NULL;p=q)
    {
        q = p->next;
        free(p);
    }
}

int main(int argc,char *argv[])
{
    //1. 初始化头节点
    struct list_node *head = NULL;
    head = init_list_head();
    
    //2. 插入节点到链表末尾
    list_add_tail(head,10);
    list_add_tail(head,20);
    list_add_tail(head,30);
    
    //3. 遍历链表,将链表中每一个节点数据域都打印出来。
    show_list_node(head);  //10 20 30
    
    //4. 头插一些节点
    list_add_head(head,8);
    list_add_head(head,5);
    list_add_head(head,3);
    
    show_list_node(head);  //3 5 8 10 20 30
    
    //5. 根据特征值查找某一个值。
    search_list_node(head,20);
    
    delete_list_node(head,10);
    
    show_list_node(head);  //3 5 8 20 30
    
    //6. 删除整条链表。
    delete_list(head);
    
    return 0;
}


 

上一篇:React Native跨平台开发学习笔记


下一篇:Microsoft .NET Core and Visual Studio信息泄露漏洞