码迷,mamicode.com
首页 > 编程语言 > 详细

4.9链表&状态机与多线程

时间:2017-07-29 22:20:32      阅读:186      评论:0      收藏:0      [点我收藏+]

标签:表示   实现   技术分享   校园   数据   相同   邻居   color   new   

4.9.1链表的引入

4.9.1.1、从数组的缺陷说起

  • (1)数组由两个缺陷。一个是数组中所有元素的类型必须一致。数组的元素个数必须事先指定,并且一旦指定后不能更改。
  • (2)如何解决数组的2个缺陷:数组的第一个缺陷考结构体解决。结构体允许其中的元素类型不相同,因此解决了数组的第一个缺陷。因此结构体是因为数组不能解决某些问题而被发明出来的
  • (3)如何解决数组的第二个缺陷?我们希望数组的大小能够实时扩展, 比如一开始我们定义了元素个数是10,后来程序运行时觉得不够因此扩展为20。普通的数组显然不行,我们可以对数组进行封装以达到目的,也可以使用一个新的数据结构来解决,这个新的数据结构就是链表
  • 总结:几乎可以这样理解:链表就是一个元素个数可以实时变大/变小的数组。

4.9.1.2、大学为什么要有新校区

  • (1)学校初建的时候就类似 变量的定义并初始化,因为旁边全是荒地,因此学校的大小是由自己定的。但是学校建立了之后,旁边慢慢的也有了其他的建筑(就类似于,这个变量分配后,内存的相邻的区域又分配了其他变量与这个变量地址相连)这时候你的校园发展感觉不够用了想要扩展,却发现邻居已经住满了,已经没法扩展了,这时候学校扩展有两条思路,第一种是拆迁,第二是搬迁,第三外部扩展
  • (2)拆迁基本行不通,不管是显示生活中还是程序运行中,因为成本太高了。
  • (3)搬迁可以行得通。程序中解决数组大小扩展的一个思路就是整体搬迁。具体的思路是:先在空白内存出简历一个大的数组,然后把原来的数组中的元素整个复制到新数组的头部。然后再释放掉原来数组的内存空间,并且把我们新的数组去替代原来的数组。这种可变数组早C语言中不支持,但是在更高级语言中java c++是支持的。
  • (4)外部扩展是做常见的,也是最合理的,他的思路就是化整为0,在原来的不动的情况下,扩展新的分基地。外部扩展在学校的例子中就是新校区。外部扩展在编程中解决数组问题的方案就是链表

4.9.1.3、链表是什么样子的?

  • (1)顾名思义,链表就是用锁链连接起来的表。

这里的表只的是一个个节点(一个个校区),节点中有内存可以用爱存储数据(所以叫表,数据表)。

这里的锁链指的是锁链各个表的方法,C语言中用来连接2块表

  • (2)链表是由若干个节点组成的(链表的各个节点是类似的),节点是由有效数据和指针组成的。有效数据区域用来存储信息完成任务的,指针区域用于指向链表的下一个节点从而构成链表。

4.9.1.4、时刻别忘了链表是用来干嘛的

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

4.9.2、单链表的实现

4.9.2.1、单链表的节点构成

  • (1)链表是由节点组成的,节点包含了有效数据部分和指针。
  • (2)定义的struct node 只是一个结构体,本身并没有变量生成,也不占内存。结构体定义相当于链表节点定义了一个模板,但是还没有一个节点,将来子啊实际创建李安表示需要一个节点时,用这个模板来复制即可。

4.9.2.2、堆内存的申请和使用

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

4.9.2.3、链表的头指针

  • (1)头指针不是一个节点,而是一个普通指针,只占4字节。头指针的类型是struct node * 类型的,所以它才能指向链表的节点
  • (2)一个典型的链表的实现就是:头指针指向链表的第一个节点,然后第一个节点中的指针指向下一个节点,然后以此类推,一直到最后一个节点。这样就构成了一个链

4.9.2.4、实战:构建一个简单的单链表

(1)目标:构建一个链表,将一个数据(譬如1,2,3三个数字)存储在链表中。

4.9.3、单链表的算法值插入节点

4.9.3.1、继续上节,访问链表中各个节点的数据

  • (1)只能用头指针,不能用各个字节自己的指针,因为实际当中我们保存链表的时候是不会保存各个节点的指针的,只能通过头指针来访问链表节点
  • (2)前一个节点内部的pNext指针能帮助我们找到下一个指节点

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

  • (1)封装时的关键点就是函数的接口(函数参数和返回值)的设计
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

struct node
{
    int data;
    struct node * pNext;
};

struct node * creat_node(int data)
{
    struct node * p = (struct node *)malloc(sizeof(struct node));
    if(NULL == p)
    {
        printf("malloc error");
        return NULL;
    }
    bzero(p, sizeof(struct node));
    p->data = data;
    p->pNext = NULL;
    return p;
}

int main(void)
{
    struct node * pHeader = NULL;
    pHeader = creat_node(1);
    pHeader->pNext = creat_node(2);
    pHeader->pNext->pNext = creat_node(3);
    printf("pHeadef->data = %d.\n", pHeader->data);
    printf("pHeader->pNext.data = %d.\n", pHeader->pNext->data);
    printf("pHeader->pNext->pNext.data = %d.\n", pHeader->pNext->pNext->data);
    return 0;
}

4.9.3.3、从链表头部插入新节点

 

4.9.3.4、从链表尾部插入新节点

(1)尾部插入链表简单,因为前面已经建立的

技术分享
struct node
{
    int data;
    struct node * pNext;
};
//创建节点
struct node * creat_node(int data)
{
    struct node * p = (struct node *)malloc(sizeof(struct node));
    if(NULL == p)
    {
        printf("malloc error");
        return NULL;
    }
    memset(p, 0, sizeof(struct node));        //给申请的堆内存清0
    //bzero(p, sizeof(struct node));
    p->data = data;
    p->pNext = NULL;
    return p;
}
//从尾部插入
int insert_tail(struct node * pHeader, struct node * new)
{
    struct node * p = pHeader;
    if(NULL != p->pNext)
    {
        p = p->pNext;
    }
    p->pNext = new;
}

int main(void)
{
    struct node * pHeader = creat_node(1);
    insert_tail(pHeader, creat_node(2));
    insert_tail(pHeader, creat_node(3));
    printf("pHeader->data = %d.\n", pHeader->data);
    printf("pHeader->pNext->data = %d.\n", pHeader->pNext->data);
    printf("pHeader->pNext->pNext->data = %d.\n", pHeader->pNext->pNext->data);
    return 0;
}
无头结点代码
技术分享
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct node
{
    int data;
    struct node * pNext;
};
//创建节点
struct node * creat_node(int data)
{
    struct node * p = (struct node *)malloc(sizeof(struct node));
    if(NULL == p)
    {
        printf("malloc error");
        return NULL;
    }
    memset(p, 0, sizeof(struct node));        //给申请的堆内存清0
    //bzero(p, sizeof(struct node));
    p->data = data;
    p->pNext = NULL;
    return p;
}
//从尾部插入
int insert_tail(struct node * pHeader, struct node * new)
{
    int cnt = 0;
    struct node * p = pHeader;
    while(NULL != p->pNext)
    {
        p = p->pNext;
        cnt++;
    }
    p->pNext = new;
    pHeader->data = cnt + 1;
}

int main(void)
{
    struct node * pHeader = creat_node(0);
    insert_tail(pHeader, creat_node(1));
    insert_tail(pHeader, creat_node(2));
    insert_tail(pHeader, creat_node(3));
    printf("beader node data = %d.\n", pHeader->data);
    printf("node1 data = %d.\n", pHeader->pNext->data);
    printf("node2 data = %d.\n", pHeader->pNext->pNext->data);
    printf("node3 data = %d.\n", pHeader->pNext->pNext->pNext->data);
    return 0;
}
有头结点代码

4.9.4、单链表的算法之插入节点续

4.9.4.1、详解链表头部插入函数

4.9.4.2、什么是头节点

(1)问题:因为我们在insert_tail中直接默认了头指针指向的有一个节点,因此如果程序中定义了头指针后就直接insert_tail后会出现段错误。我们不得不在定义头指针之后先creat_node后创建一个新节点给头指针初始化,否则不能避免这个错误,但是这样解决让程序看起来逻辑有点不太顺,看起来第一个节点和其他的节点有点不同,显得有些另类。

(2)链表还有另外一种用法,就是把头指针指向的第一个节点当作头结点使用。头结点的特点是:第一,他紧跟在头指针的后面。第二,头结点的数据部分是空的(有时候不是空的,而是存储整个链表的节点数目 )。指针部分指向下一个节点也就是第一个节点。

(3)这样看来头结点和其他节点确实不太一样。我们在创建链表时添加节点的方法也不同。头结点在创建头指针时一并创建并且和头指针关联起来,后面的真正的存储数据的节点用节点添加的函数来完成,譬如insert_node

(4)链表有没有头结点是不同的,体现在链表的插入节点、删除节点、遍历节点、解析链表的各个算法函数都不太。所以如果一个链表设计的时候有头结点,那么后面所有的算法都应该这样来处理。如果设计的时候没有头结点,那么后面所有算法都应该按照没有头结点来做,实际编程中,两种节点都有人用,所以在实际编程中,一应要看别人有没有使用头结点。

 

4.9链表&状态机与多线程

标签:表示   实现   技术分享   校园   数据   相同   邻居   color   new   

原文地址:http://www.cnblogs.com/jxjl/p/7257626.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!