标签 数据结构 下的文章

#include<iostream>
using namespace std;
struct LinkNode{
    int data;
    LinkNode *link;
};
typedef LinkNode* NodePtr;

//头部插入 
void Insert(NodePtr &head, int data){
    NodePtr temp = new LinkNode;
    temp -> link = head;
    temp -> data = data;
    head = temp;
}

//迭代输出 
void Iter(NodePtr head){
    for(NodePtr here = head; here != 0; here = here -> link){
        cout << here -> data << endl;
    }
}

//搜索 data 返回指针 
NodePtr Search(NodePtr head, int target){
    for(NodePtr here = head; here != 0; here = here -> link){
        if(here -> data == target){
            return here;
        }
    }
}

//后插入 
void AfterInit(NodePtr head, int target, int value){
    NodePtr afterMe = Search(head, target);
    NodePtr newNode = new LinkNode;
    newNode -> link = afterMe -> link;
    newNode -> data = value;
    afterMe -> link = newNode;
}

//前插入 
void BeforeInit(NodePtr head, int target, int value){
    NodePtr beforeMe = Search(head, target); //位于新元素之后 
    NodePtr newNode = new LinkNode;
    newNode -> data = value;
    for(NodePtr here = head; here != 0; here = here -> link){
        if(here -> link == beforeMe){
            //此时的 here 就是新元素之前的元素 
            here -> link = newNode;
            newNode -> link = beforeMe;
            break;
        }
    }
}

int main(){
    NodePtr head;
    head = new LinkNode;
    head -> data = 13;
    head -> link = 0;
    cout << head -> data << endl << "---" << endl;
    Insert(head, 11);
    Insert(head, 10);
    Insert(head, 9);
    Insert(head, 8);
    Iter(head);
    cout << "---" << endl;
    BeforeInit(head, 13, 12);
    Iter(head);
    return 0;
}

零、先决条件

要学习链表,您应当懂得如何使用structtypedef的用法,和 C++ 语言的一定基础。可以参考文章《数据结构简明入门教程》

链表亦可以使用进行创造,本文采用 C++ 语言的结构进行创造。

一、链表的创建

《数据结构简明入门教程》的第三部分,我们已经了解了链表。接下来将直接介绍链表的创建。

我们先构造一个最简单的链表:只有一个头指针和一个节点

链表示意图1

先决条件下,我们创造一个链表的结构。该链表包含数据部分和指针部分。

struct ListNode
{
    int data; //数据
    ListNode *link; //下一个地址的指针
};
typedef ListNode* ListNodePtr; //我们设指向节点的指针类型叫 ListNodePtr。

//Ptr是 pointer 的缩写,即指针。

我们先创造一个头指针,使之指向一个节点

ListNodePtr head;
head = new ListNode;

使用箭头操作符对该节点进行赋值。由于没有后续节点,link部分我们设为NULLnullptr

head -> data = 12;
head -> link = nullptr; //指向“空”

这样,我们就造好了一个链表。可以用以下代码验证我们真的把数据存进去了:

cout << head -> data;

二、在链表的头部插入节点

链表的头部,通常被称作首元节点。我们现在写一个函数,使得可以插入任意链表的头部。

假设头指针已经定义好为head

void InsertAfterHead(ListNodePtr &head, int data){
    //注意,上方 head 前要有“&”,因为我们需要修改 head 的值。
    
    cin >> data;   //传入新节点 data
    ListNodePtr temp; //创建一个“指向新节点的指针”
    temp = new ListNode; //该指针指向一个新节点
    
    temp -> data = data; //把 data 写入新节点
    temp -> link = head; //新节点的指针用原来的头指针
    head = temp; //把原来的头指针换做“指向新节点的指针”
    
}

三、查找节点

假设有下图的结构,我们需要查找第一项值为9的节点,并将其改为6

链表示意图2

我们设计一个函数,返回值类型是指针,以命中符合要求的链表的节点。

LinkNodePtr Search(LinkNodePtr head, int target){
    //head 为头指针,target 是要查找的目标值
    LinkNodePtr here = head; //设置一个指针,并使之指向头部
    bool gotIt = false;      //设置一个判断标志,如果标志为真则证明找到了目标
    for(; here != nullptr; here = here -> link){ 
        //当指针没有到链表尾部(为空)时进行,并搜索完一个就跳到下一个链表的地址
        
        if(here -> data == target){
            gotIt = true;
            break;
            //如果找到了,就退出循环
        }
        
    }
    
    if(gotIt) return here;
    else return nullptr;
}
LinkNodePtr target = Search(head, 9);
target -> data = 6;

对于上述代码中的 for 循环行为,有一个很酷的名字——迭代。指针here叫做迭代器。

for(LinkNodePtr here = head; here != nullptr; here = here -> link){
    //需要迭代做的事情
}

比如这样就可以逐一打印链表的数据:

for(LinkNodePtr here = head; here != nullptr; here = here -> link){
    cout << here -> data << endl;
}

四、插入节点

假如我们需要找到第一个值为6的节点,并在其后插入数据9

我们写一个插入函数,可以在指定的地址after_me后插入一个节点。

void Insert(LinkNodePtr &after_me, int data){
    LinkNodePtr temp;
    temp = new LinkNode;
    temp -> data = data;
    temp -> link = after_me -> link;
    after_me -> link = temp;
}

为了方便,我们沿用上面写好的Search函数。可以得到下面的代码:

LinkNodePtr target = Search(head, 6);
Insert(target, 9);

五、删除节点

删除节点,即从把该节点上方的指针直接指向下方节点地址。

假设指向上方节点的指针为before,指向待删除的指针为discard

before -> link = discard -> link;
delete discard; //如果要永久删除,使用 delete

六、后记

关于其他的链表,如双向链表、循环链表,请参考教材。相信你看到这里已经能够学会了。

七、参考资料

Walter Savitch 《 C++入门经典(第九版)》。

零、前言

为了弥补教材衔接处的设计问题,特查阅相关资料后书成此文,以助更好地使用教材自学。

为了方便展示,部分代码采用了中文命名的变量名,若需编译,需要使用 C++11 标准的编译器,否则会报错。

一、struct 关键字

struct可以创建结构

假设我们要创建一个名为“银行存单”的结构,其中包含“金额”、“利率”和“时长”三个信息,我们可以得到下图的结构示意。

结构,图示1.jpg

在 C++ 中,利用struct就可以这么写:

struct 银行存单
{
    double 金额;
    double 利率;
    int    时长;//按月份计算
};

注意,别忘了上面括号结尾后的分号。至于为什么会有分号,会在后面说明。

创建好结构之后,我们便可以使用结构。像定义其他变量一样定义一个银行存单类型变量test

int alpha; //变量 alpha,类型为“整型(int)”。
银行存单 test; //变量 test,类型为“银行存单”。

我们可以定义一个函数方便对类型进行数据的写入。

void 数据写入(银行存单 &temp)
{
    cout << "存入金额?";
    cin >> temp.金额;
    
    cout << "利率?";
    cin >> temp.利率;
    
    cout << "存入几个月?";
    cin >> temp.时长;
}

这样,一个完整的程序就写好了:

#include<iostream>
using namespace std;

/*
* 定义结构
*/

struct 银行存单
{
    double 金额;
    double 利率;
    int    时长;//按月份计算
};

/*
* 定义写入结构的函数
*/

void 数据写入(银行存单 &temp)
{
    cout << "存入金额?";
    cin >> temp.金额;
    
    cout << "利率?";
    cin >> temp.利率;
    
    cout << "存入几个月?";
    cin >> temp.时长;
}

int main()
{
    银行存单 test; //定义变量
    数据写入(test);//写入变量
    cout << "一共存入" << test.金额 << "元。"; //使用变量
    return 0;
}

一些更方便的写法,你可以定义一个结构类型的函数,并予以赋值:

银行存单 创建存单(double 金额, double 利率, int 时长)
{
    银行存单 temp;
    temp.金额 = 金额;
    temp.利率 = 利率;
    temp.时长 = 时长;
    return temp;
}

这样,可以在声明变量后快速赋值:

银行存单 test = 创建存单(2000, 0.003, 12);

事实上,还有这种赋值方法:

银行存单 test = {2000, 0.003, 12}; //按照顺序

此外,结构还可以套入结构

例如需要有以下结构层次:

结构,图示2.jpg

我们可以这么写:

struct Date
{
    int year;
    int month;
    int day;
};

struct person
{
    string name;
    char sex;
    Date birthday;
};

让我们回到最开始遗留的一个问题,为什么struct的花括号结尾要多一个分号?这是因为struct支持你在定义好结构后立即创建一个变量,比如:

struct 银行存单
{
    //...
} test1, test2;

但更规范一些的写法,最好是将结构变量的声明分开来写。

二、typedef 关键字

typedef用于简写或重命名类型。

假如,一个不恰当的例子——你不喜欢“int”这个词,你希望它变为“cafe”,即:

int num; //不喜欢
cafe num; //喜欢

有了typedef,你可以这么做:

typedef int cafe;
cafe num;

同样的,你可以给上文的结构起名。假如我们命名一个结构shopTicket,我们希望 以ST称呼这个结构,我们可以这么做:

typedef shopTicket ST;

或者在定义结构的时候就用上:

typedef struct shopTicket
{
    //...
} ST;

也就是说,下面两段代码等价。

struct shopTicket
{
    //...
};
typedef shopTicket ST;
typedef struct shopTicket
{
    //...
} ST;

我个人觉得,用前一种分开定义的写法更利于阅读。

三、链表

假如存在一种这样的模式:

结构,图示3.jpg

一个结构,包含数据和下一个结构地址的指针,这样构成的就是单链表。为了知道第一个结构的位置,我们还需要一个额外的head指针指向第一个结构。像这样的一个结构,我们称之为节点(list node)

struct ListNode
{
    int data; //数据
    ListNode *link; //下一个地址的指针
};
typedef ListNode* ListNodePtr; //我们设指向节点的指针类型叫 ListNodePtr。

//Ptr是 pointer 的缩写,即指针。

我们先创建一个指向第一个结构的指针,叫head

ListNodePtr head;

然后,我们像图里那样,向该结构的数据部分(即data)写入12

(*head).data = 12;

由于圆点操作符.的优先级高于*,所以我们需要用括号把*head围住。

这样写看起来很麻烦,对吧?在 C++ 中,我们有更好的写法——箭头操作符->。也就是说,上面的语句可以简化为

head->data = 12;

其实这很像自然语言——自head指向的data,值为12

利用相同的方法,你可以创建多个ListNode类型的变量,逐一连接起来他们。

对于最后一个节点,他的指针应该宣告结束,否则乱指的指针可能会造成灾难性后果。我们通常设置为NULL

在 C++ 中,NULL等同于0。尽管如此,我们通常还是把结束节点的指针设置为NULL而不是0NULL意味着链的结束。由于NULL0的相等,有时候会带来麻烦。在 C++11 中,引入了专门代表空指针的字面量nullptr。可选地,我们可以用这个代替NULL

四、结语

至此,你已经对数据结构以及链表做了初步的入门,相信接下来你已经可以借助书本学习了。祝你好运。

五、参考资料

  1. Walter Savitch 《 C++ 入门经典(第九版)》ISBN 978-7-302-40297-8
  2. C typedef | 菜鸟教程