C/C++:使用数组模拟链表

来源:互联网 发布:软件开发 书籍 知乎 编辑:程序博客网 时间:2024/05/29 09:32

C/C++:使用数组模拟链表

链表中每一个node都由两部分组成:

1.真实的数据;
2.下一个数据的位置;

通常的实现可以使用指针作为指示下一个数据的位置的方式,当没有一个合法的数据存在,就将指针部分置为NULL,这个NULL是一个不在有效范围内的值

链表不仅可以使用指针来实现,也可以使用数组来实现,区别(思路)在于:

指示下一个数据的位置的实现,使用一个整型int值来指明,而不是一个指针。

现在有两个数组:

int data[100], right[100];

data用来保存数据,right用来指明当前数据的下一个数据所在data中的位置

注意,data不一定是个整型数组,可以是任意数组,毕竟没有谁规定数据只能是整型。

上面的话有点绕,但是仔细想想指针的实现,不也是,当前的指针值,指明下一个节点所在内存空间中的地址吗?

对比两者:

之前的整个内存空间,映射到了现在的right的100个整型数组空间中;
之前的指针,映射到了right的一个整型变量;
由指针指明的地址,现在变成了由right[pos]指明地址;
之前的地址是一个指针,现在的地址是一个整型数

…等等。

right和指针的相同之处在于,他们都指明了下一个节点的位置(这个位置是一个整型数或者是一个指针来保存的)。

下面先看一个Demo:

#include <stdio.h>#include <stdlib.h>int main(){    // 定义100个node    int data[100], right[100];    for (int i = 0; i < 100; i++)    {           data[i] = 0;        right[i] = -1;     }       // serial:0 5 9 13 56 78 97    // 我想要将上面的数字按序存储到模拟链表中    // 将数字0存储到0位置的node    data[0] = 0;    // 我想要将数字5存储到数字0的下一个节点,这里可以是1    // 思考:这里可以是别的吗?    // 回答:当然可以,可以是2,可以是3...    right[0] = 1;    data[right[0]] = 5;    right[right[0]] = 2;    data[right[1]] = 9;    right[right[1]] = 3;    data[right[2]] = 13;     // 注意,这里将下一个数字存储到了位置50    right[right[2]] = 50;     // 这里实际存储的是数组中位置50data[right[3]] = 56;     right[right[3]] = 4;    data[right[50]] = 78;     right[right[50]] = 24;     data[right[4]] = 97;     right[right[4]] = -1;     int next = 0;    while (next != -1)     {           printf("%d ", data[next]);        next = right[next];    }    printf("\n");    return 0;}
[test1280@localhost ~]$ !ggcc -o main main.c -std=c99[test1280@localhost ~]$ ./main0 5 9 13 56 78 97

程序主要由两部分组成,一部分是赋值,一部分是遍历输出。

上面的程序在执行完赋值部分后,数组应该是这样子的:

data    0   5   9   13  78  97  56right   1   2   3   50  24  -1  4index   0   1   2   3   4   24  50

注意index指明了实际的数组中的位置。

先把上面的程序,动手画一下,写一写,看明白了再往下~

上面的程序并没有按序排放,我们当然可以按照序列顺序来进行:

#include <stdio.h>#include <stdlib.h>int main(){    // serial:0 5 9 13 56 78 97    // 我想要将上面的数字按序存储到模拟链表中    int original[7] = {0, 5, 9, 13, 56, 78, 97};    int ori_len = 7;    // 定义100个node    int data[100], right[100];    for (int i = 0; i < 100; i++)    {           data[i] = 0;        right[i] = -1;     }       int next = 0;    for (int i = 0; i < ori_len; i++)    {           data[next] = original[i];        right[next] = i + 1;        next = i + 1;    }       // 最后一个手工去置为-1    right[next - 1] = -1;     next = 0;    while (next != -1)     {           printf("%d ", data[next]);        next = right[next];    }       printf("\n");    return 0;}
[test1280@localhost ~]$ ./main0 5 9 13 56 78 97

想一想为什么最后一个手动置-1?哈哈。可以画图看一看。

(题外话:之前的初始化时不严密的,我应该规定合法数据是大于0的,初始化为-1,下面修正。)

下面我们看一看完整的插入和删除:

注意:我对数据的结构有了优化,使用了一个链表结构体来代表一个链表对象,其中next代表链表起始位置,当然,这个位置是相对于data成员来说的,否则无意义。

#include <stdio.h>#include <stdlib.h>struct LinkList{    int *data;    int *right;    // 当next为负代表链表中无数据,否则代表起始节点的绝对位置    int next;    // 链表的最大长度    int maxLen;};/********************************************************************************************************** * 函数名称:init * 参数列表:linkList-指向一个链表对象;data-数据指针;right-下标指针;data_len-data的长度;original-源数据指针;ori_len-源数据长度 * 函数描述:使用一个数组初始化一个链表 * 返回值  :负数代表初始化失败 * 备注    :对data/right的读写合法性由主调函数保证(故可能出现越界读写) * Author  :test1280 * History :2017/05/14 * *******************************************************************************************************/int init(struct LinkList *linkList, int *data, int *right, int data_len, int *original, int ori_len){    // 入参校验    if (linkList == NULL || data == NULL || right == NULL || original == NULL || data_len < 0 || ori_len < 0)    {        return -1;    }    linkList->data = data;    linkList->right = right;    // 即使data_len比实际的data/right长度短也无所谓,因为是主调函数栈上的空间    linkList->maxLen = data_len;    linkList->next = -1;    int i;    for (i = 0; i < data_len; i++)    {        // 初始化为-1,任何数据非负才是合法的数据        linkList->data[i] = -1;        // 初始化为-1,任何下标非负才是合法的下标        linkList->right[i] = -1;    }    // 使用init函数初始化链表都将从0位置开始赋值    int next = 0;    for (i = 0; i < ori_len; i++)    {        linkList->data[next] = original[i];        linkList->right[next] = i + 1;        next = i + 1;    }    // 若初始化后链表中有数据,对linkList->next进行修改    // 若能进入之前的for循环,必能进入此处的if块    if (ori_len > 0)    {        linkList->right[next - 1] = -1;        linkList->next = 0;    }    return 0;}/********************************************************************************************************** * 函数名称:insert * 参数列表:linkList-指向一个链表对象;obj-要插入的数据 * 函数描述:插入一个合法数据(非负)到一个逻辑上从小到大的有序链表中 * 返回值  :若插入成功则返回插入的绝对位置;若插入失败则为负值 * 备注    :假定此链表在逻辑上已经由小到大排好序了;插入一个不合法的数据将失败 * Author  :test1280 * History :2017/05/14 * *******************************************************************************************************/int insert(struct LinkList *linkList, int obj){    // 入参校验:注意,linkList->next<0是合法的入参     if (linkList == NULL || obj < 0)    {        return -1;    }    // 注意这里隐含了对next<0的处理    int next = 0;    // linkList->next >= 0    if (linkList->next > 0)    {        next = linkList->next;    }    // prePos指向不大于要插入的数据(obj)的最大值的绝对位置    int prePos = -1;    // 如果尚未达到尾边界    while (linkList->data[next] >= 0)    {        // 如果大于obj        if (linkList->data[next] > obj)        {            break;        }        prePos = next;        next = linkList->right[next];    }    // nowPos指明当前的节点的绝对位置    int nowPos = 0;    // 找一个空槽放入数据(空槽的判断标准是是否数据非负)    while (nowPos < linkList->maxLen && linkList->data[nowPos] >= 0)    {        nowPos ++;    }    // 遍历完所有的data[nowPos]发现都有数据,即链表已满,插入失败    if (nowPos == linkList->maxLen)    {        return -1;    }    // 将要插入的数据放置在空槽中    linkList->data[nowPos] = obj;    // 如果插入到非首节点处    if (prePos >= 0)    {        // 将当前的位置的right修改为前驱节点的right值        linkList->right[nowPos] = linkList->right[prePos];        // 将前驱节点的right值修改为当前节点的绝对位置        linkList->right[prePos] = nowPos;    }    // 插入到首节点有两种情况:第一种是插入到非空链表中;第二种是插入到空链表中    else    {        // 插入到非空链表中还需要将后面的链起来        if (linkList->next >= 0)        {            linkList->right[nowPos] = linkList->next;        }        linkList->next = nowPos;    }    return nowPos;}/********************************************************************************************************** * 函数名称:delete * 参数列表:next-指明起始下标;data-数据指针;right-下标指针;obj-要删除的数据 * 函数描述:从链表中删除一个数据;如果被删除的数据在链表中不存在则认为删除失败 * 返回值  :若删除成功则返回起始节点的绝对位置;若删除失败则为负值 * 备注    : * Author  :test1280 * History :2017/05/14 * *******************************************************************************************************/int delete(struct LinkList *linkList, int obj){    // 参数校验:不为NULL、链表中有数据、被删除的是一个合法的数据    if (linkList == NULL || linkList->next < 0 || obj < 0)    {        return -1;    }    int start = linkList->next;    int next = start;    int prePos = -1;    while (next != -1)    {        if (linkList->data[next] == obj)        {            break;        }        prePos = next;        next = linkList->right[next];    }    // 未在链表中找到obj    if (next == -1)    {        return -1;    }    // 如果是第一个节点    if (next == start)    {        // 数据域置-1        linkList->data[start] = -1;        // 修改起始位置        linkList->next = linkList->right[start];        // 下标域置-1        linkList->right[start] = -1;    }    // 如果要删除的不是第一个节点    else    {        linkList->right[prePos] = linkList->right[next];        linkList->data[next] = -1;        linkList->right[next] = -1;    }    return linkList->next;}/********************************************************************************************************** * 函数名称:traversal * 参数列表:next-指明起始下标;data-数据指针;right-下标指针; * 函数描述:编译输出一个链表的所有值 * 返回值  :返回0代表成功 * 备注    : * Author  :test1280 * History :2017/05/14 * *******************************************************************************************************/int traversal(struct LinkList *linkList){    int next = linkList->next;    printf("start traversal...\n");    while (next >= 0)    {        printf("%d ", linkList->data[next]);        next = linkList->right[next];    }    printf("\n");    printf("end traversal...\n");    return 0;}int main(){    // serial:0 5 9 13 56 78 97    // 我想要将上面的数字按序存储到模拟链表中    int original[7] = {4, 5, 9, 13, 56, 78, 97};    int ori_len = 7;    // 定义100个node    int data[100], right[100];    int data_len = 100;    struct LinkList linkList;    init(&linkList, data, right, data_len, original, ori_len);    traversal(&linkList);    insert(&linkList, 65);    traversal(&linkList);    insert(&linkList, 0);    traversal(&linkList);    insert(&linkList, 97);    insert(&linkList, 98);    traversal(&linkList);    printf("delete ...\n");    int start = 0;    start = delete(&linkList, 65);    traversal(&linkList);    start = delete(&linkList, 78);    traversal(&linkList);    start = delete(&linkList, 4);    traversal(&linkList);    start = delete(&linkList, 0);    traversal(&linkList);    start = delete(&linkList, 98);    traversal(&linkList);    start = delete(&linkList, 97);    start = delete(&linkList, 97);    traversal(&linkList);    return 0;}

额,代码略微有点长。但是就核心代码来说就那么几句:

insert:

插入的时候可以随便(从前到后)找一个空位置(如何判空)来放置要插入的数据,然后需要修改right域:如果新插入的数作为首节点该如何处理;如果新插入的数不是作为首节点又该如何处理。其实无非就是将该链接的连接起来,将链表的起始位置(next成员)修改修改等等。

delete:

删除一个元素将这个元素的前后链接起来,记得将当前的节点的数据域与指针域置-1,如果要是删除头节点,还需要修改next域数据…

无论如何吧,其实都和指针实现的链表非常相似。

对以上代码来说,这只是一个初步的模型,还有很多后续可以优化的地方去处理~

里面也可能有很多的Bug,大家看的时候千万别太相信我~哈哈。

大家可以自己测试测试,有问题留言~我来Debug。

真心想说一句:数组模拟实现链表感觉还没有指针实现链表简洁易懂。。。

其他资料:

1.http://blog.csdn.net/jianxin1009/article/details/7952069
2.http://www.cppblog.com/rakerichard/archive/2010/03/16/109854.aspx
3.http://blog.csdn.net/ly_624/article/details/51273541
4.http://www.cnblogs.com/student-note/p/6616878.html


好吧,刚刚看了下其他说法,有一种叫静态链表,实际和上面用数组模拟链表差不多。

只不过它的特点是每个Node都是独立的,额,可以有很明显的对象界定,而不像我们上面介绍的两个数组来维护…

静态链表的结构体定义:

struct Node{    // 数据域    int data;    // 游标域(下标域)    int cur;};

其实不论是指针也好,数组模拟也罢,还有静态链表,归根结底都是一个数据域一个指针域,指针域用来保存逻辑上下一个节点的位置,至于这里的指针域用什么办法来指明下一个节点的位置,就可以分为真正的指针以及数字游标…

其实上面的用数组来模拟链表,不也是一种静态链表吗?

2 0
原创粉丝点击