------------------------siwuxie095
线性表
这里介绍 线性表,那么什么是线性表呢?
比较官方的说法 即 线性表是 n 个数据元素的有限序列
通俗的理解,如下:
有这么一段数字,其中有 43、52、41 … 它们没有什么顺序,也没有
什么规律
要说明的问题是:当我们把这段数字排列起来,成线性展开,这时就
叫做 线性表
除此之外,还有比较复杂一点的线性表,如下:
上表中的内容:小明,男,28;李磊,男,32 … 其实也构成了
一个 线性表
显然,定义中的数据元素,可以是简单的数据,也可以是复杂的
数据
对于线性表来说,大致分为两大类:
一大类叫做 顺序表,其实在线性表中,顺序表 就是用数组来表达的,
它的优势就在于访问速度快、搜索能力强,因为数组本身就带有天然
的下标,它是与内存的地址直接相关的
另一大类叫做 链表,也叫做 线性链表 或 线性表的链式表达方式,链
表又分为:静态链表、单链表、循环链表、双向链表
无论是哪种链表方式,都有一个字很重要,即 链
那么这个 链 究竟要表达什么意思?链表的基本特征又是什么样的?链
表之所以重要又是为什么?
顺序表
为什么要在线性表中去区分顺序表和链表呢?因为它们之间互为补充
顺序表的优缺点:
顺序表的优点非常明显,就是它在进行遍历和寻址时非常快
如:拿着一个数据元素在顺序表中定位
因为顺序表是基于数组的,所以在做这些操作时,效率都会很高
但它的缺点也是显而易见的
如:想要向顺序表中插入一个数据元素
你就会发现,当我们在插入的时候,当前位置向后的所有元素,都必须
要向后移一个位置,才能够顺利的插入
同理:当删除一个元素时,在要删除位置向后的所有元素,都必须要向
前移动一个位置,才能顺利的删除
那么有没有一种方式使得插入和删除的操作,效率高一点呢?这就要用
到链表了
链表
(1)单链表
单链表的特点:单向性
结点:
结点其实就是线性表中的数据元素,只不过此时,它的元素分为
两部分:一部分是数据域,另一部分是指针域
指针域用来指向下一个结点,下一个结点又分为数据域和指针域,
而其指针域又指向下一个结点,直到找到最后一个结点为止,它
的指针域指向 NULL,也就是指向 空
(2)循环链表
循环链表与单链表略有不同,它最大的不同在于:
在尾结点,即 最后一个结点的位置,它的指针域又指向了头结点,指向
头结点之后,你会发现它就变成了一个 环,即 循环链表
(3)双向链表
双向链表,也叫做 双链表,它的每一个结点由三部分组成:其中
两部分都是指针域,一部分是数据域
为什么在双向链表中要有两部分指针域呢?
因为其中一个指针域是走正向,而另外一个指针域走反向
换言之,即 其中一个指针域,是从头结点不停的去寻找,能够找到尾节点,
而另一个指针域,则是从尾结点不停的去寻找,能够找到头结点
(4)静态链表
对于某些计算机语言来说,它没有指针,却又想做链表,
就可以通过数组来完成
对于一个数组来说,数组天然就具有编号,即 下标,如
上图的 0、1、2、3、4
对于静态链表的每一个结点来说,又分为两部分:一部分
是所谓的指针域,它起到指针的作用,另一部分是数据域
那么指针域是如何来寻址的呢?
当进入这个数组之后,指针域的第一个位置就是要找的头结点
头结点的指针域中是 1,指向下标 1 所在的结点;
该结点的指针域中是 4,指向下标 4 所在的结点;
该结点的指针域中是 2,指向下标 2 所在的结点;
该结点的指针域中是 3,指向下标 3 所在的结点;
该结点的指针域中是 0,指向下标 0 所在的结点
下标 0 所在的结点 即 头结点,这代表当前的链表走到了最后
应用场景
线性表的一个比较常见的场景就是 通讯录
大家的手机上都有通讯录,通讯录的特点就是:时常要向通讯录中加
一些内容,还要从通讯录中把一些内容删掉
无论是添加、删除,还是在打电话时想要搜索,这个时候都要用到线
性表,所以线性表在通讯录中也可以得到最佳的实践
此外还有 一元多项式,它其实解决的是一个数学问题,如下:
p0 后面跟的是 x0,p1 后跟的是 x1,p2 后面跟的是 x2 … pn 后面跟的是 xn
其中,p0、p1、p2 … pn 都表示 系数,而因为只有 x 一个变量,没有 y、z
等其他变量,所以称之为 一元
程序 1:顺序表
List.h:
#ifndef LIST_H #define LIST_H
class List { public: List(int size); //创建顺序表 ~List(); //销毁顺序表 void ClearList(); //清空顺序表 bool ListEmpty(); //顺序表为空 int ListLength(); //顺序表长度 bool GetElem(int i, int *e); //获取指定元素 int LocateElem(int *e); //寻找第一个满足e的数据元素的位序 bool PriorElem(int *currentElem,int *preElem); //获取指定元素的前驱 bool NextElem(int *currentElem, int *nextElem); //获取指定元素的后继 void ListTraverse(); //遍历顺序表 bool ListInsert(int i, int *e); //在第i个位置插入元素 bool ListDelete(int i, int *e); //删除第i个位置的元素 private: int *m_pList; //指向顺序表的指针 int m_iSize; //顺序表的容量大小 int m_iLength; //当前顺序表的长度 };
#endif |
List.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
List::List(int size) { //初始化顺序表的容量 m_iSize = size; //分配内存 m_pList = new int[size]; //初始化顺序表中已有元素的个数 m_iLength = 0; }
List::~List() { delete []m_pList; m_pList = NULL; }
//清空顺序表不等于释放当前顺序表的内存 //只是将顺序表中已经存放的元素全部清空 void List::ClearList() { //只需将m_iLength赋值0即可,至于已经存放在内存中的值 //完全可以忽略不计 // //因为未来再往顺序表中放值时可以直接将原有值覆盖掉 m_iLength = 0; }
bool List::ListEmpty() { if (0 == m_iLength) { return true; } return false;
//或采用另一种形式: //return m_iLength == 0 ? true : false; }
int List::ListLength() { return m_iLength; }
bool List::GetElem(int i, int *e) { //判断 i 是否合法 if (i < 0 || i >= m_iSize) { return false; }
*e = m_pList[i]; return true; }
int List::LocateElem(int *e) { //与已有元素进行比较 for (int i = 0; i < m_iLength; i++) { if (m_pList[i] == *e) { return i; } } //返回-1表示没有找到相应的数据元素 return -1; }
bool List::PriorElem(int *currentElem, int *preElem) { int temp = LocateElem(currentElem); //如果为-1,则根本没有这个元素 if (-1 == temp) { return false; } else { //如果为0,即第一个元素,则没有前驱 if (0 == temp) { return false; } else { *preElem = m_pList[temp-1]; return true; } } }
bool List::NextElem(int *currentElem, int *nextElem) { int temp = LocateElem(currentElem); //如果为-1,则根本没有这个元素 if (-1 == temp) { return false; } else { //如果为m_iLength-1,即当前已有元素的最后一个,则没有后继 if (m_iLength-1 == temp) { return false; } else { *nextElem = m_pList[temp + 1]; return true; } } }
void List::ListTraverse() { for (int i = 0; i < m_iLength; i++) { cout << m_pList[i] << endl; } cout << endl; }
bool List::ListInsert(int i, int *e) { if (i<0 || i>m_iLength || m_iLength == m_iSize) { return false; } //先从后往前的移动i位置及以后的已有元素 //如果先插入,则会将i位置的元素覆盖掉 for (int k = m_iLength; k >= i; k--) { m_pList[k + 1] = m_pList[k]; }
m_pList[i] = *e;
//插入元素后,顺序表长度加1 m_iLength++;
return true; }
bool List::ListDelete(int i, int *e) { if (i < 0 || i >= m_iLength) { return false; } //将第i个位置的元素拷贝出来 *e = m_pList[i];
//直接移动元素进行覆盖即可,从前往后进行移动 //这就已经删除了第i个位置的元素 for (int k = i+1; k < m_iLength; k++) { m_pList[k - 1] = m_pList[k]; }
//删除元素后,顺序表长度减1 m_iLength--;
return true; } |
main.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
//顺序表: //示例:3 5 7 2 9 1 8 //该顺序表比较简单,都是 int 型整数 // //介绍两个概念:前驱和后继 //如:指定元素 2,它的前驱就是 7,它的后继就是 9 // //说的更简单一点,所谓前驱就是指定元素的前边的元素, //而所谓后继就是指定元素的后边的元素 // //有的也把紧邻的元素叫做直接前驱和直接后继 int main(void) { //因为在插入时,多向后赋值了一位,所以初始化的 //空间至少比实际插入数值的个数多1 List *p = new List(8);
int e1 = 3; int e2 = 5; int e3 = 7; int e4 = 2; int e5 = 9; int e6 = 1; int e7 = 8; int temp = 0;
p->ListInsert(0, &e1);//3 p->ListInsert(1, &e2);//5 p->ListInsert(2, &e3);//7 p->ListInsert(3, &e4);//2 p->ListInsert(4, &e5);//9 p->ListInsert(5, &e6);//1 p->ListInsert(6, &e7);//8 p->ListTraverse();
cout << "length:" << p->ListLength() << endl;
p->GetElem(0,&temp); cout << "temp:" << temp << endl;
cout << "index:" << p->LocateElem(&temp) << endl;
p->PriorElem(&e4, &temp); cout << "preElem:" << temp << endl;
p->NextElem(&e4, &temp); cout << "nextElem:" << temp << endl; p->ListDelete(0, &temp); cout << "#:" << temp << endl;
p->ClearList(); if (p->ListEmpty()) { cout << "empty" << endl; }
delete p; p = NULL;
system("pause"); return 0; } |
运行一览:
程序 2:
Coordinate.h:
#ifndef COORDINATE_H #define COORDINATE_H
#include <ostream> using namespace std;
class Coordinate { friend ostream &operator<<(ostream &out, Coordinate &coor); public: Coordinate(int x = 0, int y = 0); void printCoordinate(); //==的重载,传入的参数实际上第二个参数,第一个参数是this指针 bool operator==(Coordinate &coor); private: int m_iX; int m_iY; };
//当Coordinate的对象或引用作参数时,会调用拷贝构造函数, //因为这里Coordinate的数据成员比较简单,没有涉及到指针, //就使用默认拷贝构造函数即可 #endif |
Coordinate.cpp:
#include "Coordinate.h" #include <iostream> using namespace std;
Coordinate::Coordinate(int x, int y) { m_iX = x; m_iY = y; }
void Coordinate::printCoordinate() { cout << "(" << m_iX << "," << m_iY << ")" << endl; }
bool Coordinate::operator==(Coordinate &coor) { if (this->m_iX == coor.m_iX && this->m_iY == coor.m_iY) { return true; } return false; }
ostream &operator<<(ostream &out, Coordinate &coor) {
cout << "(" << coor.m_iX << "," << coor.m_iY << ")" << endl; return out; } |
List.h:
#ifndef LIST_H #define LIST_H
#include "Coordinate.h"
class List { public: List(int size); //创建顺序表 ~List(); //销毁顺序表 void ClearList(); //清空顺序表 bool ListEmpty(); //顺序顺序空 int ListLength(); //顺序表长度 //获取指定元素 bool GetElem(int i, Coordinate *e); //寻找第一个满足e的数据元素的位序 int LocateElem(Coordinate *e); //获取指定元素的前驱 bool PriorElem(Coordinate *currentElem, Coordinate *preElem); //获取指定元素的后继 bool NextElem(Coordinate *currentElem, Coordinate *nextElem); void ListTraverse(); //遍历顺序表 bool ListInsert(int i, Coordinate *e); //在第i个位置插入元素 bool ListDelete(int i, Coordinate *e); //删除第i个位置的元素 private: Coordinate *m_pList; //指向顺序表的指针 int m_iSize; //顺序表的容量大小 int m_iLength; //当前顺序表的长度 };
#endif |
List.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
List::List(int size) { //初始化顺序表的容量 m_iSize = size; //分配内存 m_pList = new Coordinate[size]; //初始化顺序表中已有元素的个数 m_iLength = 0; }
List::~List() { delete[]m_pList; m_pList = NULL; }
//清空顺序表不等于释放当前顺序表的内存 //只是将顺序表中已经存放的元素全部清空 void List::ClearList() { //只需将m_iLength赋值0即可,至于已经存放在内存中的值 //完全可以忽略不计 // //因为未来再往顺序表中放值时可以直接将原有值覆盖掉 m_iLength = 0; }
bool List::ListEmpty() { if (0 == m_iLength) { return true; } return false;
//或采用另一种形式: //return m_iLength == 0 ? true : false; }
int List::ListLength() { return m_iLength; }
bool List::GetElem(int i, Coordinate *e) { //判断 i 是否合法 if (i < 0 || i >= m_iSize) { return false; }
*e = m_pList[i]; return true; }
int List::LocateElem(Coordinate *e) { //与已有元素进行比较 for (int i = 0; i < m_iLength; i++) { if (m_pList[i] == *e) { return i; } } //返回-1表示没有找到相应的数据元素 return -1; }
bool List::PriorElem(Coordinate *currentElem, Coordinate *preElem) { int temp = LocateElem(currentElem); //如果为-1,则根本没有这个元素 if (-1 == temp) { return false; } else { //如果为0,即第一个元素,则没有前驱 if (0 == temp) { return false; } else { *preElem = m_pList[temp - 1]; return true; } } }
bool List::NextElem(Coordinate *currentElem, Coordinate *nextElem) { int temp = LocateElem(currentElem); //如果为-1,则根本没有这个元素 if (-1 == temp) { return false; } else { //如果为m_iLength-1,即当前已有元素的最后一个,则没有后继 if (m_iLength - 1 == temp) { return false; } else { *nextElem = m_pList[temp + 1]; return true; } } }
void List::ListTraverse() { for (int i = 0; i < m_iLength; i++) { //在Coordinate.h中完成了对输出运算符<<的重载 //所以可以直接用cout进行输出 cout << m_pList[i] << endl;
//或使用以下方法进行输出 //m_pList[i].printCoordinate(); } }
bool List::ListInsert(int i, Coordinate *e) { if (i<0 || i>m_iLength || m_iLength == m_iSize) { return false; } //先从后往前的移动i位置及以后的已有元素 //如果先插入,则会将i位置的元素覆盖掉 for (int k = m_iLength; k >= i; k--) { m_pList[k + 1] = m_pList[k]; }
m_pList[i] = *e;
//插入元素后,顺序表长度加1 m_iLength++;
return true; }
bool List::ListDelete(int i, Coordinate *e) { if (i < 0 || i >= m_iLength) { return false; } //将第i个位置的元素拷贝出来 *e = m_pList[i];
//直接移动元素进行覆盖即可,从前往后进行移动 //这就已经删除了第i个位置的元素 for (int k = i + 1; k < m_iLength; k++) { m_pList[k - 1] = m_pList[k]; }
//删除元素后,顺序表长度减1 m_iLength--;
return true; } |
main.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
int main(void) { //因为在插入时,多向后赋值了一位,所以初始化的 //空间至少比实际插入数值的个数多1 List *p = new List(8);
Coordinate c1(3, 5); Coordinate c2(5, 7); Coordinate c3(6, 8); Coordinate temp(0, 0);
p->ListInsert(0, &c1); p->ListInsert(1, &c2); p->ListInsert(2, &c3);
p->ListTraverse();
cout << "length:" << p->ListLength() << endl; cout << endl;
p->GetElem(0,&temp); cout << "temp:" << temp; cout << "index:" << p->LocateElem(&temp) << endl; cout << endl;
p->PriorElem(&c2, &temp); cout << "preElem:" << temp << endl;
p->NextElem(&c2, &temp); cout << "nextElem:" << temp << endl; p->ListDelete(0, &temp); cout << "#:" << temp << endl;
p->ClearList(); if (p->ListEmpty()) { cout << "empty" << endl; }
delete p; p = NULL;
system("pause"); return 0; } |
运行一览:
程序 3:链表
Node.h:
#ifndef NODE_H #define NODE_H
class Node { public: //为了操作的方便,将数据域和指针域都定义在public下 int data; //数据域 Node *next; //指针域指向下一个结点 void printNode(); };
#endif |
Node.cpp:
#include "Node.h" #include <iostream> using namespace std;
void Node::printNode() { //只需打印数据域即可 cout << data << endl; } |
List.h:
#ifndef LIST_H #define LIST_H
#include "Node.h"
class List { public: List(); //创建链表(1) ~List(); //销毁链表(5) void ClearList(); //清空链表(4) bool ListEmpty(); //链表为空(2) int ListLength(); //链表长度(3) //获取指定i位置结点:需要从头结点开始顺藤摸瓜找到指定位置(10) bool GetNode(int i, Node *pNode); //拿着当前给定的结点,去找与这个结点中数据域相同的第一个结点,并返回位序(11) int LocateNode(Node *pNode); //获取指定结点的前驱:从头结点开始寻找到pCurrentNode,然后...(12) bool PriorNode(Node *pCurrentNode, Node *pPreNode); //获取指定结点的后继:从头结点开始寻找到pCurrentNode,然后...(13) bool NextNode(Node *pCurrentNode, Node *pNextNode); void ListTraverse(); //遍历链表(14) bool ListInsert(int i, Node *pNode); //在第i个位置插入结点(8) bool ListDelete(int i, Node *pNode); //删除第i个位置的结点(9) bool ListInsertHead(Node *pNode); //从头开始插入结点(6) bool ListInsertTail(Node *pNode); //从尾开始插入结点(7) private: Node *m_pList; //指向链表的指针 int m_iLength; //当前链表的长度 };
//作为链表来说,它的每一个元素都是它的结点
#endif |
List.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
//(1) List::List() { //初始化链表首先要定义一个头结点 //数据域与指针域分别为 0 和 NULL // //对于一个链表来说,它的第一个结点即头结点的数据域 //是没有意义的,而它的指针域,一开始的情况下也没有意义 //因为作为第一个结点来说,它也是最后一个结点 //如果再挂载新结点,再将next指向新结点 m_pList = new Node; m_pList->data = 0; m_pList->next = NULL;
//将链表的长度初始化为 0 //注意:虽然已经从堆中分配了内存,已经有了一个结点 //但这个结点并不算在当前的链表中 m_iLength = 0; }
//(5) //析构函数与ClearList()其实有着异曲同工之妙,二者的区别就在于: //析构函数将构造函数中申请的第一个结点也释放掉,ClearList()则 //将其保留下来,而释放掉后面的所有结点 // //即只有第一个结点是否释放的区别 List::~List() { //调用ClearList(),将除了头结点m_pList之外的 //所有其他结点都删除掉了 ClearList(); //只需再删除m_pList即可 delete m_pList; m_pList = NULL; }
//(4) //ClearList()的实现原理: //举个例子:如果我们面临着一群敌人,这些敌人彼此之间是通过 //单线进行联系的,那么怎么把这群敌人全部消灭掉呢? //首先我们要先找到一个敌人,他是团伙组织的最上线,然后审问 //他:他的下线是谁,他交代了之后,就将他干掉,然后找到他的 //下线,再去审问...直到找到一个敌人,他再也交代不出来自己的 //下线了,把抓到的最后一个敌人再干掉,那么工作也就完成了 void List::ClearList() { //找到第一条线索,顺着线索m_pList的next找到第一个敌人currentNode //因为m_pList是头结点,并不算在链表中,所以不算第一个敌人 Node *currentNode = m_pList->next; //对敌人进行审问,如果不为空,就顺藤摸瓜 while (currentNode != NULL) { //先审一审当前的敌人currentNode的下线next是谁 Node *temp = currentNode->next; //currentNode交代后,就毫不客气的干掉他 delete currentNode; //这时,temp变成了当前要审问的敌人 currentNode = temp; }
//当currentNode为空时,敌人也就全部被消灭了 //将m_pList重新置为NULL m_pList = NULL; }
//(2) bool List::ListEmpty() { if (0 == m_iLength) { return true; } return false;
//或采用另一种形式: //return m_iLength == 0 ? true : false; }
//(3) int List::ListLength() { return m_iLength; }
//(10) bool List::GetNode(int i, Node *pNode) { //判断 i 是否合法 if (i < 0 || i >= m_iLength) { return false; }
//先保存一下m_pList Node *currentNode = m_pList; //通过for循环来找到第i个位置 for (int k = 0; k <= i; k++) { //遍历 currentNode = currentNode->next; } pNode->data = currentNode->data; return true; }
//(11) //看看链表中有没有哪个结点的数据域与pNode的数据域相同 //如果有就将这个结点的位序返回出来 //注意:是返回第一个相同的结点 int List::LocateNode(Node *pNode) { //先取到m_pList的值 Node *currentNode = m_pList; int count = 0; //不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) { currentNode = currentNode->next; if (currentNode->data == pNode->data) { return count; } count++; } //一个结点都没找到,返回-1 return -1; }
//(12) bool List::PriorNode(Node *pCurrentNode, Node *pPreNode) { //先取到m_pList的值 Node *currentNode = m_pList; Node *tempNode = NULL;
//不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) { tempNode = currentNode; currentNode = currentNode->next; if (currentNode->data == pCurrentNode->data) { //默认头结点不算在链表中, //即头结点的下一个结点的是没有前驱的 if (tempNode == m_pList) { return false; } pPreNode->data = tempNode->data; //找到第一个符合条件的结点就返回true return true; } } return false; }
//(13) bool List::NextNode(Node *pCurrentNode, Node *pNextNode) { Node *currentNode = m_pList;
//不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) { currentNode = currentNode->next; if (currentNode->data == pCurrentNode->data) { //如果当前结点是最后一个结点,则没有后继 if (currentNode->next == NULL) { return false; } pNextNode->data = currentNode->next->data; //找到第一个符合条件的结点就返回true return true; } } return false; }
//(14) void List::ListTraverse() { //先拿到m_pList Node *currentNode = m_pList; while (currentNode->next != NULL) { currentNode = currentNode->next; currentNode->printNode(); } }
//(8) //i即插入的位置: //如果是0,即插入在头结点的后边, //如果是m_iLength,即插入在当前链表的最后边 bool List::ListInsert(int i, Node *pNode) { //判断i是否合法 if (i<0 || i>m_iLength) { return false; } //先保存一下m_pList Node *currentNode = m_pList; //通过for循环来找到第i个位置的上一个位置 for (int k = 0; k < i; k++) { //遍历 currentNode = currentNode->next; }
Node *newNode = new Node; if (NULL == newNode) { //内存申请有可能失败 return false; } newNode->data = pNode->data; //原来currentNode的下一个结点变成newNode的下一个结点 newNode->next = currentNode->next; //而newNode成为了currentNode的下一个结点 //即将newNode插入到了整个链表当中 currentNode->next = newNode; m_iLength++;
return true; }
//(9) //i即要删除的结点的位置:如果为0,则删除头结点的下一个结点 //注意:i不能等于m_iLength bool List::ListDelete(int i, Node *pNode) { //如果i等于m_iLength就意味着你在删尾结点的下一个结点 if (i < 0 || i >= m_iLength) { return false; } //先保存一下m_pList Node *currentNode = m_pList; //如果要删除一个结点,就应该能够找到当前结点的上一个结点 //才容易通过上一个结点再去连接要删除的这个结点的下一个结点 //从而将要删除的结点分离出来并删除 Node *currentNodeBefore = NULL; //通过for循环来找到第i个位置 for (int k = 0; k <= i; k++) { //遍历 currentNodeBefore = currentNode; currentNode = currentNode->next; } currentNodeBefore->next = currentNode->next; //将要删除结点的数据域赋值给传入进来的参数pNode pNode->data = currentNode->data; delete currentNode; currentNode = NULL;
m_iLength--;
return true; }
//(6) //将结点插入到头结点的后边 bool List::ListInsertHead(Node *pNode) { //先保存一下m_pList的next Node *temp = m_pList->next; //将传入进来的pNode的数据域保存到一个新的结点里 //所以需要先定义一个新结点,注意:一定要从堆中申请内存, //如果从栈中申请内存,此函数执行完毕,内存就被回收掉了 Node *newNode = new Node;
if (NULL == newNode) { //内存申请有可能失败 return false; }
//对于pNode的指针域并不用关心,只拿数据域即可 newNode->data = pNode->data; //将头结点m_pList的next指向新申请的newNode m_pList->next = newNode; //而newNode的next则需要指向原来m_pList的next newNode->next = temp;
m_iLength++;
//插入成功,向外发出成功的信号 return true; }
//(7) //将结点插入到链表的最后边,即链表的尾部 bool List::ListInsertTail(Node *pNode) { //先保存一下m_pList Node *currentNode = m_pList; //如果当前结点currentNode的next不为空, //就在while循环里做遍历的操作 while (currentNode->next != NULL) { //将currentNode的next赋值给currentNode //就相当于来到了下一个结点 currentNode = currentNode->next; }
//当currentNode为空了,也就跳出了while循环, //此时的currentNode就是最后一个结点 //将传入进来的pNode挂载进去,也即插入 Node *newNode = new Node; if (NULL == newNode) { //内存申请有可能失败 return false; } newNode->data = pNode->data; //此时,newNode作为最后一个结点 newNode->next = NULL; currentNode->next = newNode;
m_iLength++;
return true; } |
main.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
int main(void) { List *p = new List();
Node n1;//Node n1(); n1.data = 3; Node n2; n2.data = 4; Node n3; n3.data = 5; Node n4; n4.data = 6; Node n5; n5.data = 7; Node temp;
/*p->ListInsertHead(&n1); p->ListInsertHead(&n2); p->ListInsertHead(&n3); p->ListInsertHead(&n4);*/ p->ListInsertTail(&n1); p->ListInsertTail(&n2); p->ListInsertTail(&n3); p->ListInsertTail(&n4);
p->ListInsert(1, &n5);
p->GetNode(1, &temp); cout << "#:" << temp.data << endl;
p->PriorNode(&n5, &temp); cout << "preNode:" << temp.data << endl; p->NextNode(&n5, &temp); cout << "nextNode:" << temp.data << endl; /*p->ListDelete(1,&temp); cout << "delNoe:" << temp.data << endl;*/
//ListInsertHead()的遍历结果是逆序 //ListInsertTail()的遍历结果是顺序 p->ListTraverse();
delete p; p = NULL;
system("pause"); return 0; } |
运行一览:
程序 4:通讯录
Person.h:
#ifndef PERSON_H #define PERSON_H
#include <string> #include <ostream> using namespace std;
//通讯录: //联系人信息(姓名、电话)作为结点的数据域 class Person { friend ostream &operator<<(ostream &out,Person &person);
public: string name; string phone; Person &operator=(Person &person); bool operator==(Person &person); };
#endif |
Person.cpp:
#include "Person.h"
Person &Person::operator=(Person &person) { this->name = person.name; this->phone = person.phone; return *this; }
bool Person::operator==(Person &person) { if (this->name == person.name && this->phone == person.phone) { return true; } return false; }
ostream &operator<<(ostream &out, Person &person) { out << person.name << "---" << person.phone << endl; return out; } |
Node.h:
#ifndef NODE_H #define NODE_H
#include "Person.h"
class Node { public: //为了操作的方便,将数据域和指针域都定义在public下 Person data; //数据域 Node *next; //指针域指向下一个结点 void printNode(); };
#endif |
Node.cpp:
#include "Node.h" #include <iostream> using namespace std;
void Node::printNode() { //只需打印数据域即可 cout << data << endl; } |
List.h:
#ifndef LIST_H #define LIST_H
#include "Node.h"
class List { public: List(); //创建链表(1) ~List(); //销毁链表(5) void ClearList(); //清空链表(4) bool ListEmpty(); //链表为空(2) int ListLength(); //链表长度(3) //获取指定i位置结点:需要从头结点开始顺藤摸瓜找到指定位置(10) bool GetNode(int i, Node *pNode); //拿着当前给定的结点,去找与这个结点中数据域相同的第一个结点,并返回位序(11) int LocateNode(Node *pNode); //获取指定结点的前驱:从头结点开始寻找到pCurrentNode,然后...(12) bool PriorNode(Node *pCurrentNode, Node *pPreNode); //获取指定结点的后继:从头结点开始寻找到pCurrentNode,然后...(13) bool NextNode(Node *pCurrentNode, Node *pNextNode); void ListTraverse(); //遍历链表(14) bool ListInsert(int i, Node *pNode); //在第i个位置插入结点(8) bool ListDelete(int i, Node *pNode); //删除第i个位置的结点(9) bool ListInsertHead(Node *pNode); //从头开始插入结点(6) bool ListInsertTail(Node *pNode); //从尾开始插入结点(7) private: Node *m_pList; //指向链表的指针 int m_iLength; //当前链表的长度 };
//作为链表来说,它的每一个元素都是它的结点
#endif |
List.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
//(1) List::List() { //初始化链表首先要定义一个头结点 // //对于一个链表来说,它的第一个结点即头结点的数据域 //是没有意义的,而它的指针域,一开始的情况下也没有意义 //因为作为第一个结点来说,它也是最后一个结点 //如果再挂载新结点,再将next指向新结点 m_pList = new Node; m_pList->data.name = "#"; m_pList->data.phone = "#"; m_pList->next = NULL;
//将链表的长度初始化为 0 //注意:虽然已经从堆中分配了内存,已经有了一个结点 //但这个结点并不算在当前的链表中 m_iLength = 0; }
//(5) //析构函数与ClearList()其实有着异曲同工之妙,二者的区别就在于: //析构函数将构造函数中申请的第一个结点也释放掉,ClearList()则 //将其保留下来,而释放掉后面的所有结点 // //即只有第一个结点是否释放的区别 List::~List() { //调用ClearList(),将除了头结点m_pList之外的 //所有其他结点都删除掉了 ClearList(); //只需再删除m_pList即可 delete m_pList; m_pList = NULL; }
//(4) //ClearList()的实现原理: //举个例子:如果我们面临着一群敌人,这些敌人彼此之间是通过 //单线进行联系的,那么怎么把这群敌人全部消灭掉呢? //首先我们要先找到一个敌人,他是团伙组织的最上线,然后审问 //他:他的下线是谁,他交代了之后,就将他干掉,然后找到他的 //下线,再去审问...直到找到一个敌人,他再也交代不出来自己的 //下线了,把抓到的最后一个敌人再干掉,那么工作也就完成了 void List::ClearList() { //找到第一条线索,顺着线索m_pList的next找到第一个敌人currentNode //因为m_pList是头结点,并不算在链表中,所以不算第一个敌人 Node *currentNode = m_pList->next; //对敌人进行审问,如果不为空,就顺藤摸瓜 while (currentNode != NULL) { //先审一审当前的敌人currentNode的下线next是谁 Node *temp = currentNode->next; //currentNode交代后,就毫不客气的干掉他 delete currentNode; //这时,temp变成了当前要审问的敌人 currentNode = temp; }
//当currentNode为空时,敌人也就全部被消灭了 //将m_pList重新置为NULL m_pList = NULL; }
//(2) bool List::ListEmpty() { if (0 == m_iLength) { return true; } return false;
//或采用另一种形式: //return m_iLength == 0 ? true : false; }
//(3) int List::ListLength() { return m_iLength; }
//(10) bool List::GetNode(int i, Node *pNode) { //判断 i 是否合法 if (i < 0 || i >= m_iLength) { return false; }
//先保存一下m_pList Node *currentNode = m_pList;
//通过for循环来找到第i个位置 for (int k = 0; k <= i; k++) { //遍历 currentNode = currentNode->next; } pNode->data = currentNode->data;
return true; }
//(11) //看看链表中有没有哪个结点的数据域与pNode的数据域相同 //如果有就将这个结点的位序返回出来 //注意:是返回第一个相同的结点 int List::LocateNode(Node *pNode) { //先取到m_pList的值 Node *currentNode = m_pList; int count = 0; //不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) { currentNode = currentNode->next; if (currentNode->data == pNode->data) { return count; } count++; } //一个结点都没找到,返回-1 return -1; }
//(12) bool List::PriorNode(Node *pCurrentNode, Node *pPreNode) { //先取到m_pList的值 Node *currentNode = m_pList; Node *tempNode = NULL;
//不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) { tempNode = currentNode; currentNode = currentNode->next; if (currentNode->data == pCurrentNode->data) { //默认头结点不算在链表中, //即头结点的下一个结点的是没有前驱的 if (tempNode == m_pList) { return false; } pPreNode->data = tempNode->data; //找到第一个符合条件的结点就返回true return true; } } return false; }
//(13) bool List::NextNode(Node *pCurrentNode, Node *pNextNode) { Node *currentNode = m_pList;
//不断去对比每一个结点,直到最后一个结点为止 while (currentNode->next != NULL) {
currentNode = currentNode->next; if (currentNode->data == pCurrentNode->data) { //如果当前结点是最后一个结点,则没有后继 if (currentNode->next == NULL) { return false; } pNextNode->data = currentNode->next->data; //找到第一个符合条件的结点就返回true return true; }
} return false; }
//(14) void List::ListTraverse() { //先拿到m_pList Node *currentNode = m_pList; while (currentNode->next != NULL) { currentNode = currentNode->next; currentNode->printNode(); } }
//(8) //i即插入的位置: //如果是0,即插入在头结点的后边, //如果是m_iLength,即插入在当前链表的最后边 bool List::ListInsert(int i, Node *pNode) { //判断i是否合法 if (i<0 || i>m_iLength) { return false; }
//先保存一下m_pList Node *currentNode = m_pList; //通过for循环来找到第i个位置的上一个位置 for (int k = 0; k < i; k++) { //遍历 currentNode = currentNode->next; }
Node *newNode = new Node; if (NULL == newNode) { //内存申请有可能失败 return false; } newNode->data = pNode->data; //原来currentNode的下一个结点变成newNode的下一个结点 newNode->next = currentNode->next; //而newNode成为了currentNode的下一个结点 //即将newNode插入到了整个链表当中 currentNode->next = newNode;
m_iLength++;
return true; }
//(9) //i即要删除的结点的位置:如果为0,则删除头结点的下一个结点 //注意:i不能等于m_iLength bool List::ListDelete(int i, Node *pNode) { //如果i等于m_iLength就意味着你在删尾结点的下一个结点 if (i < 0 || i >= m_iLength) { return false; }
//先保存一下m_pList Node *currentNode = m_pList; //如果要删除一个结点,就应该能够找到当前结点的上一个结点 //才容易通过上一个结点再去连接要删除的这个结点的下一个结点 //从而将要删除的结点分离出来并删除 Node *currentNodeBefore = NULL; //通过for循环来找到第i个位置 for (int k = 0; k <= i; k++) { //遍历 currentNodeBefore = currentNode; currentNode = currentNode->next; } currentNodeBefore->next = currentNode->next; //将要删除结点的数据域赋值给传入进来的参数pNode pNode->data = currentNode->data; delete currentNode; currentNode = NULL;
m_iLength--;
return true; }
//(6) //将结点插入到头结点的后边 bool List::ListInsertHead(Node *pNode) { //先保存一下m_pList的next Node *temp = m_pList->next; //将传入进来的pNode的数据域保存到一个新的结点里 //所以需要先定义一个新结点,注意:一定要从堆中申请内存, //如果从栈中申请内存,此函数执行完毕,内存就被回收掉了 Node *newNode = new Node;
if (NULL == newNode) { //内存申请有可能失败 return false; }
//对于pNode的指针域并不用关心,只拿数据域即可 newNode->data = pNode->data; //将头结点m_pList的next指向新申请的newNode m_pList->next = newNode; //而newNode的next则需要指向原来m_pList的next newNode->next = temp;
m_iLength++;
//插入成功,向外发出成功的信号 return true; }
//(7) //将结点插入到链表的最后边,即链表的尾部 bool List::ListInsertTail(Node *pNode) { //先保存一下m_pList Node *currentNode = m_pList; //如果当前结点currentNode的next不为空, //就在while循环里做遍历的操作 while (currentNode->next != NULL) { //将currentNode的next赋值给currentNode //就相当于来到了下一个结点 currentNode = currentNode->next; }
//当currentNode为空了,也就跳出了while循环, //此时的currentNode就是最后一个结点 //将传入进来的pNode挂载进去,也即插入 Node *newNode = new Node; if (NULL == newNode) { //内存申请有可能失败 return false; } newNode->data = pNode->data; //此时,newNode作为最后一个结点 newNode->next = NULL; currentNode->next = newNode;
m_iLength++;
return true; } |
main.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
int main(void) {
List *p = new List();
Node n1;//Node n1(); n1.data.name = "tester1"; n1.data.phone = "123456"; Node n2; n2.data.name = "tester2"; n2.data.phone = "234567"; Node temp;
/*p->ListInsertHead(&n1); p->ListInsertHead(&n2);*/
p->ListInsertTail(&n1); p->ListInsertTail(&n2);
//ListInsertHead()的遍历结果是逆序 //ListInsertTail()的遍历结果是顺序 p->ListTraverse();
delete p; p = NULL;
system("pause"); return 0; } |
运行一览:
程序 5:基于程序 4,修改其中的 main.cpp
main.cpp:
#include "List.h" #include "stdlib.h" #include <iostream> using namespace std;
int menu(); void createPerson(List *pList); void deletePerson(List *pList);
int main(void) {
int userOrder = 0; List *p = new List();
while (userOrder != 4) { userOrder = menu(); switch (userOrder) { case 1: cout << "用户指令--->>新建联系人:" << endl; createPerson(p); cout << endl; break; case 2: cout << "用户指令--->>删除联系人:" << endl; deletePerson(p); cout << endl; break; case 3: cout << "用户指令--->>浏览通讯录:" << endl; p->ListTraverse(); break; case 4: cout << "用户指令--->>退出通讯录:" << endl; cout << endl; break; default: break; } }
delete p; p = NULL;
system("pause"); return 0; }
int menu() { //显示通讯录功能菜单 cout << "功能菜单" << endl; cout << "1.新建联系人" << endl; cout << "2.删除联系人" << endl; cout << "3.浏览通讯录" << endl; cout << "4.退出通讯录" << endl; cout << "请输入:"; int order = 0; cin >> order; return order; }
void createPerson(List *pList) { Node node; Person person;
cout << "请输入姓名:"; cin >> person.name;
cout << "请输入电话:"; cin >> person.phone;
node.data = person; //因为这个node是从栈中实例化的,所以在 //ListInsertTail()中只取它的数据域即可 //不要直接将这个node直接挂到链表上 //否则createPerson()一执行完,内存一回收, //就会出现内存的访问错误 pList->ListInsertTail(&node); }
void deletePerson(List *pList) { Node node;
cout << "请输入姓名:"; cin >> node.data.name;
cout << "请输入电话:"; cin >> node.data.phone;
if (-1 == pList->LocateNode(&node)) { cout << "无此联系人!" << endl; return; } Node temp; pList->ListDelete(pList->LocateNode(&node),&temp); } |
运行一览:
【made by siwuxie095】