数据结构与算法学习 第1季02 链表的基本功能 C++实现

2015年学习计划安排:

http://www.cnblogs.com/cyrus-ho/p/4182275.html

尝试用C++实现了双向链表类LinkList,基本功能是在位置i插入结点和删除位置i的结点。

首先是结点类,每个结点有数据data,指向前一个结点的指针front和指向后一个结点的指针next

class Node
{
public:
    int data;
    Node* next;
    Node* front;
public:
    Node();
    Node(int data);
    Node(int data, Node* nextPtr, Node* frontPtr);
};

结点类的实现:

#include "stdafx.h"
#include "Node.h"

Node::Node()
{
    this->data = 0;
    this->next = NULL;
    this->front = NULL;
}

Node::Node(int data)
{
    this->data = data;
    this->next = NULL;
    this->front = NULL;
}

Node::Node(int data, Node* nextPtr, Node* frontPtr)
{
    this->data = data;
    this->next = nextPtr;
    this->front = frontPtr;
}

双向链表类的实现

#include "Node.h"

class LinkList
{
public:
    int length;
    Node headNode;
    Node rearNode;

public:
    LinkList();
    bool IsLinkListEmpty();
    void Display(int mode);
    void InsertNode(int i, int data);
    void DeleteNode(int i);
};

初始化空链表,注意headNode和rearNode会分别调用Node类的无参的构造方法。

LinkList::LinkList()
{
    this->length = 0;
    this->headNode.next = &(this->rearNode);
    this->rearNode.front = &(this->headNode);
}

如果想显示调用Node类的有参的构造方法,需要用初始化列表的方式来给headNode和rearNode初始化。

LinkList::LinkList():headNode(10),rearNode(10)
{
    this->length = 0;
    this->headNode.next = &(this->rearNode);
    this->rearNode.front = &(this->headNode);
}

当然头结点和尾结点的数据是没有意义的,实际上我们只需要用到头结点的next和尾结点的front,不过为了统一操作,将单纯一个指针扩为一个结点。

双向链表中结点的数量为length,不包括头尾结点。

判断链表是否为空。

bool LinkList::IsLinkListEmpty()
{
    if(this->length==0)
    {
        return true;
    }
    return false;
}

在控制台输出链表,可以选择反向输出。

void LinkList::Display(int mode)
{
    if (mode == 0)
    {
        Node* ptr = this->headNode.next;
        for (int i = 0; i < this->length; i++)
        {
            std::cout<<ptr->data<<std::endl;
            ptr = ptr->next;
        }
    }
    else
    {
        Node* ptr = this->rearNode.front;
        for (int i = 0; i < this->length; i++)
        {
            std::cout<<ptr->data<<std::endl;
            ptr = ptr->front;
        }
    }
    return;
}

规律是,ptr一开始是头结点的指针域,那么,执行i次

ptr = ptr->next;

ptr指向的结点在链表中的位置就是i(注意i从0开始计算,一次都不执行自然就是指向结点0---当然也可能是指向尾结点,如果链表为空)

在位置i插入结点,即插入的结点在链表的位置为i。

void LinkList::InsertNode(int i, int data)
{
    if ( (i > this->length) || (i < 0) )
    {
        std::cout<<"Error!!!!!!!!!"<<std::endl;
    }
    else
    {
        Node* temp = new Node(data);
        Node* ptr = this->headNode.next;
        for (int k = 0; k < i; k++)
        {
            ptr = ptr->next;
        }

        temp->next = ptr;
        temp->front = ptr->front;

        ptr->front = temp;
        temp->front->next = temp;

        this->length++;
    }
    return;
}

首先判断位置i是否合法。对于一个长度为length的链表,各结点(不含头尾结点)的位置是0,1,...,length-1,合法的插入位置是0,1,...,length。

接下来是找到链表中的某next指针,其指向的结点是原链表中的结点i。即 ptr: Node i-1 -> Node i,当i=0时,Node i-1是头结点。当i=length时,Node i是尾结点。

Node* ptr = this->headNode.next;for (int k = 0; k < i; k++)
{
      ptr = ptr->next;
}

上述代码实现了这个功能。特别地,当i=0时,循环体一次都没有执行,ptr: Head Node -> Node 0,当i=length时,循环体执行了length+1次,ptr: Node i -> Rear Node。

然后,生成一个新的结点,其(指向它的)指针为temp:

 Node* temp = new Node(data);

这个结点是要插入的结点,所以其位置应该为i,因此它的next应该指向原来链表中的结点i,而原来链表中的结点i的地址,正是ptr:

temp->next = ptr;

而这个新结点的front应该指向原来链表中的结点i-1,而原来链表中的结点i-1的地址,是ptr->front。(ptr现在指向的是原链表的结点i,原链表的结点i的front则指向原链表的结点i-1):

temp->front = ptr->front;

然后,原链表的结点i的front,应该指向这个新结点,这个新结点的地址是temp。(ptr现在指向的是原链表的结点i,这个结点的front应该由指向原链表的结点i-1变为指向新结点):

ptr->front = temp;

接下来,原链表的结点i-1的next,也应该指向这个新结点。(temp的front现在就是指向了原链表的结点i-1,将这个结点的next由指向原链表的结点i改为指向新结点):

temp->front->next = temp;

最后,length+1。

在完成这个函数的过程中我曾犯了3个错误:

1.一开始我没有track指针域,而是不停地copy结点本身,获得原来的结点i-1和结点i的copy,然后在上边做指针变换,所以实际上我create了3个结点(新结点temp,结点i-1的copy和结点i的copy),然后把这3个结点连在一起,这对原链表一点儿影响都没有,因为我处理的是结点i-1和结点i的COPY!

2.track指针域,在做指针断开/交换操作的最后一步(接下来,原链表的结点i-1的next,也应该指向这个新结点)时,写了

ptr = temp;

的确,原链表的结点i-1的next,和ptr的值是一样的(都是指向原链表的结点i),但是上面的代码是改了ptr,而不是改了原链表的结点i-1的next!

3.在生成新结点的时候,一开始我用了以下方式:

Node temp(data);

函数内执行结果并没有错(当然Node*的地方要变为Node),但是函数返回后在主程序中没有得到正确的结果。我推测应该是函数局部变量的函数结束时会被自动销毁,所以在主程序中获得的链表,在读到新结点的时候就会得到垃圾值。

在插入结点的经验上,实现删除结点的操作就轻松很多了:

void LinkList::DeleteNode(int i)
{
    if ( (i > this->length - 1) || (i < 0) )
    {
        std::cout<<"Error!!!!!!!!!"<<std::endl;
    }
    else
    {
        Node* ptr = this->headNode.next;
        for (int k = 0; k < i; k++)
        {
            ptr = ptr->next;
        }
        Node* temp = ptr;
        ptr->next->front = ptr->front;
        ptr->front->next = ptr->next;
        this->length--;
        delete temp;
        temp = NULL;
    }
    return;
}
时间: 2025-01-05 20:18:09

数据结构与算法学习 第1季02 链表的基本功能 C++实现的相关文章

数据结构与算法学习 第0季 学前入门

2015年学习计划安排: http://www.cnblogs.com/cyrus-ho/p/4182275.html 数据结构:计算机内部数据的组织形式和存储方法.(不同的数据结构在实现同一个功能的时候,算法的选用不一定一样) 常用的数据结构:一对一 --- 线性结构:一对多 --- 树结构:多对多 --- 图结构 所谓的一对一,就是从前一个数据通过某种方式可以找到下一个数据(最多只有一个).线性结构主要包括:(important!从存储形式上分)顺序表和链表:(从逻辑功能上分)栈和队列.(同

数据结构与算法学习 第1季03 栈 队列 树 图

2015年学习计划安排: http://www.cnblogs.com/cyrus-ho/p/4182275.html 栈:LIFO的线性表 队列:FIFO的线性表 树:(递归定义)n个结点的有穷集合,对非空树,有且仅有一个称为根的结点,n>1时,其余结点分为m个互不相交的有限集,而每一个集合本身也是一棵树,并称为根的子树 图:任意两个数据元素之间都可以存在关系的组织结构 二叉树的存储:可以选择用多重链表的数据结构 二叉树的遍历:先序,中序,后序 图的存储:邻接矩阵(不适于存储稀疏图)与邻接表

数据结构与算法学习 第1季01 顺序表 链表

2015年学习计划安排: http://www.cnblogs.com/cyrus-ho/p/4182275.html 顺序表:顺序存储结构的线性表.所谓顺序存储结构,就是指用一组连续地址的内存单元来存储整张线性表的存储结构.(因此按序遍历数据很方便,直接做指针偏移就可以了.) 常用操作 A)向顺序表中第i个位置插入元素item 1. 判断插入位置是否合法 2. 将i-1以后的元素后移一个元素的位置(注意静态顺序表和动态顺序表的差异)--- 从原来最后一个元素开始操作到原来的第i个元素,依次后移

数据结构与算法学习之路:背包问题的贪心算法和动态规划算法

一.背包问题描述: 有N种物品和一个重量为M的背包,第i种物品的重量是w[i],价值是p[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大. 二.解决方法: 1.贪心算法:贪心算法基于的思想是每一次选择都作当前最好的选择,这样最后的结果虽然不一定是最优解,但是也不会比最优解差很多. 举个例子说明可能好懂一些:一帮基友去聚餐,菜是一份一份上的,我每一次夹菜都只夹牛肉/海鲜吃,可能到最后我吃的牛肉/海鲜很多,但不一定代表我吃掉的东西的总价值最高,但是相对来说价值也很高

数据结构和算法设计专题之---判断单链表中是否有环,环的长度,环的入口节点

题目: 给定一个单链表,只给出头指针head: 1.如何判断是否存在环? 2.如何知道环的长度? 3.如何找出环的连接点在哪里? 4.带环链表的长度是多少? 解法: 1.对于问题1,使用追赶的方法,设定两个指针slow.fast,从头指针开始,每次分别前进1步.2步.如存在环,则两者相遇:如不存在环,fast遇到NULL退出. 2.对于问题2,记录下问题1的碰撞点p,slow.fast从该点开始,再次碰撞所走过的操作数就是环的长度s. 3.问题3:有定理:碰撞点p到连接点的距离=头指针到连接点的

数据结构和算法学习

Algorithms, 4th Edition 不过一遍都不好意思说你学过算法           学习资料: 怎样学算法? 如何学习数据结构?

数据结构与算法——学习整理记录

===注:此文由本人结合网上资源整理总结而来,仅代表个人的学习与理解,如有错漏,欢迎指正!=== # 1. 数据结构 ## 1.1 数据结构是什么? 数据结构,直白地理解,就是研究数据的逻辑关系与存储方式的一门学科. 可以简单的分为:数据的逻辑结构(逻辑关系)和数据的存储结构(物理结构). 它是以某种形式将数据组织在一起的集合,它不仅存储数据,还支持访问和处理数据的操作. ### 1.1.1 数据的逻辑结构 数据的逻辑结构,简单地理解,就是指的数据之间的逻辑关系. 数据之间的逻辑关系可简单的分为

数据结构和算法学习总结01 绪论

数据结构实际上是数据元素之间的关系的集合 数据结构分为    1.逻辑结构      2.物理结构(逻辑结构的数据元素的存储方式)                            逻辑结构分为  1.集合结构   数据元素无关系,只是属于一个集合                            2.线性结构   数据元素间1对1的关系                            3.树形结构   数据元素间1对多的关系                            

.Net学习 第2季02 C#面向对象继承1

.Net 视频学习第2季 C#面向对象 面向对象继承1 查找类的命名空间快捷键:alt+shift+F10 项目A中有类X,要在项目B中使用X的话,首先在项目B的引用当中添加项目A,然后在项目B的代码中添加using A;(假设项目A类X的命名空间就是A)或者在使用X时采用A.X的格式. 记录运行时间 Stopwatch sw = new Stopwatch(); sw.Start(); // 代码 sw.Stop(); Console.WriteLine(sw.Elapsed); Start(