リニアテーブル - リスト
また、記事を作ったままにする理由
// TODO NullGirlfrindException
退屈な2列以上の事実を無視してください......
オーダー表の不備を補うためにどのように?
まず、リニアテーブルを学習し、すぐに(順次記憶構造)テーブルを注文すると呼ばご連絡させていただきます、以前のシーケンステーブルの長所と短所を分析した後、それはすぐにリード素子にアクセスすることができますが、ということは非常に明確であるが、このようインサートとしてやなどの操作を削除して、私たちは多数の素子を移動する必要が対処する上で、あまり効率的で、この問題を改善したり、解決する方法はありますか?
まず、テーブルの順序操作を挿入または削除がそれの要素を動かす含むことになる理由を検討する必要がありますか?
、挿入または欠失につながった、各要素は次の数1に基づいていることを意味し、隣接する要素を配置、順番に保存されている - おい、質問が表示されます最大の特徴の順位表を中心に展開します要素の背後にある要素の動作位置は、いくつかの変更を必要としている、まだリニアメモリのために、あなたが想像することができるはずですし、突然嘔吐、巨大な群衆の背後に混雑したランク内のシーンから学生を挿入香りの移動バックスペースを、群衆が大きすぎる場合には、再び、それはいくつかの時間がかかるだろう並ん
さて、人と人との間にそれほど混雑してはいけない、両方のは、特定の位置から滞在するそれぞれの人の間に少し隙間から、まあ、道のようですが、フィリングとの学生交流担当教師は、道路の多くを実行する必要が、多くの時間を無駄にしたときにI(誰かが)トラバースすることを意味し、私は、これを話す学校博物館、死ぬことはない、あなたは、このような法律を入れて、私の小さなを行うことはできませんホールは、より多くのスペースの複雑さを意味し、手放すことができません
私達はちょうど「キュー」の基本的な前提の周りですが、我々は考えることができる方法は、我々は単にラインアップしていない、非常に満足のいくものではない、とそれを解決するためのより良い方法を持ってすることはできないのですか?
効果的な方法:
学生(要素)が、自分の位置気軽に駅を見つけるためにしましょう、しかし、あなたは彼らの次のクラスメートの位置に関して知っておく必要があり、それはまた、この二十から二接触道へのアクセス(トラバース)のスペースの問題を解決しますが、チーム全体(配列)、最も重要なのは、それはあまりにも多くの学生に影響を与えないように(要素)と言うによる学生の間で一種の不在に、(要素)ラインアップ、隣接する機能を、学生を挿入して残すことであるのみあなたは人々があなたの間で何が起こったのかわからない、2人の学生はとにかく、困難になろうと通信する前と後に挿入して位置する必要があります
単一のリスト - まあ、アイデアがあり、我々は、最も一般的なのリストを見て
単一リンクリストの基本的な構造
このリストは、なぜ、単一のリストも呼ばれていますか?これは、それが唯一のアドレスフィールドが含まれているので、それが何を意味しているのですか?
私たちは、本を読んでどのような種類のものをキューイング順序テーブルにリストを拒否し、私たちはあるべき二つの隣接する要素間の相関をある程度許容しなければならないので、私たちは、対応する次の要素に到達することができ、各要素を作ることにしました
そして、私たちはその保管するために追加の場所を手配するために、各要素を与える必要があり、この時、次の要素のメモリアドレスは、ドメイン情報記憶素子が呼び出され、ポインタまたはドメイン・アドレス・フィールドもドメインポインタとして知られて格納されている情報、ポインタまたはチェーン、
私たちは、構造物の絵で彼を見て
構造用語集
- ヘッドポインタ:最初の変数のノードアドレスへのポインタ
- ヘッドポインタ持つ一本鎖識別先頭ポインタとそう頻繁に、演技の名前は、単一リンクリストを表し
- 最初のノード:単一リンクリストの前の最初のノードに接続されたノードは、それがヘッドノードと呼ばれ、何の直接の前駆体を持っていません
- 長さ情報が格納されないことがあり、それは、ポスト、または追加情報を格納するための線形テーブルとしてモニターすることができます
- ポインタ記憶素子第1ノードドメインアドレス
- 第一要素ノード:記憶素子の最初のノード
なぜ、ヘッドノードに接続
さんに説明してみましょう:
最初のノードは、次に、ヘッドポインタがNULLをポイントする、一本鎖でない場合、リストが空の場合は、ある場合に最初のノードに結合されている場合、単一のリンクされたリストが空であるかどうか、ヘッド・ポインタは、最初のノードを指し、その結果、空のリストとなります空でないリスト処理契約
時間前に挿入または欠失接合素子となるように、第1の要素は、バック同様の動作は、追加の決意ブランチを生成する必要性は、アルゴリズムをより簡単になり
(挿入を説明する例)鉛ノード、挿入または削除する要素が前に一つだけの要素(ここでは最初のノード)のポインタ、第一の要素ノードの前に他の位置に操作と同じままである場合に挿入エレメント・ポインタ・フィールドは、元の第2の要素を指しながら、要素を挿入する視野点
而无头结点的情况由于,首元结点前没有元素,只能通过修改head的前后关系,所以导致了 与在别的位置插入或删除元素的操作不同,在实现这两个功能的时候就需要额外的写一个判断语句来判断插入的位置是不是首元结点之前的位置,增加了分支,代码不够简洁
总结:头结点的存在使得空链表与非空链表处理一致,也方便对链表首元结点前结点的插入或删除操作
单链表的类型定义
线性表的抽象数据类型定义
我们在给出单链表的定义之前我们还是需要先引入我们线性表的抽象数据类型定义
#ifndef _LIST_H_
#define _LIST_H_
#include<iostream>
using namespace std;
class outOfRange{};
class badSize{};
template<class T>
class List {
public:
// 清空线性表
virtual void clear()=0;
// 判空,表空返回true,非空返回false
virtual bool empty()const=0;
// 求线性表的长度
virtual int size()const=0;
// 在线性表中,位序为i[0..n]的位置插入元素value
virtual void insert(int i,const T &value)=0;
// 在线性表中,位序为i[0..n-1]的位置删除元素
virtual void remove(int i)=0;
// 在线性表中,查找值为value的元素第一次出现的位序
virtual int search(const T&value)const=0;
// 在线性表中,查找位序为i的元素并返回其值
virtual T visit(int i)const=0;
// 遍历线性表
virtual void traverse()const=0;
// 逆置线性表
virtual void inverse()=0;
virtual ~List(){};
};
/*自定义异常处理类*/
class outOfRange :public exception { //用于检查范围的有效性
public:
const char* what() const throw() {
return "ERROR! OUT OF RANGE.\n";
}
};
class badSize :public exception { //用于检查长度的有效性
public:
const char* what() const throw() {
return "ERROR! BAD SIZE.\n";
}
};
#endif
单链表的类型定义
#ifndef _SEQLIST_H_
#define _SEQLIST_H_
#include "List.h"
#include<iostream>
using namespace std;
template<class elemType>
//elemType为单链表存储元素类型
class linkList:public List<elemType> {
private:
//节点类型定义
struct Node {
//节点的数据域
elemType data;
//节点的指针域
Node *next;
//两个构造函数
Node(const elemType value, Node *p = NULL) {
data = value;
next = p;
}
Node(Node *p = NULL) {
next = p;
}
};
//单链表的头指针
Node *head;
//单链表的尾指针
Node *tail;
//单链表的当前长度
int curLength;
//返回指向位序为i的节点的指针
Node *getPostion(int i)const;
public:
linkList();
~linkList();
//清空单链表,使其成为空表
void clear();
//带头结点的单链表,判空
bool empty()const {return head -> next == NULL;}
//返回单链表的当前实际长度
int size()const {return curLength;}
//在位序i处插入值为value的节点表长增1
void insert(int i, const elemType &value);
//删除位序为i处的节点,表长减1
int search(const elemType&value)const;
//查找值为value的节点的前驱的位序
int prior(const elemType&value)const;
//访问位序为i的节点的值,0定位到首元结点
elemType visit(int i)const;
//遍历单链表
void traverse()const;
//头插法创建单链表
void headCreate();
//尾插法创建单链表
void tailCreate();
//逆置单链表
void inverse();
};
单链表上的基本运算实现
(一) 单链表的初始化-构造函数
单链表的初始化就是创建一个带头节点的空链表,我们不需要设置其指针域,为空即可
template<class elemType>
linkList<elemType>::linkList() {
head = tail = new Node();
curLength=0;
}
注意:new 操作符代表申请堆内存空间,上述代码中应该判断是否申请成功,为简单,默认为申请成功,实际上如果系统没有足够的内存可供使用,那么在申请内存的时候会报出一个 bad_alloc exception 异常
(二) 析构函数
当单链表对象脱离其作用域时,系统自动执行析构函数来释放单链表空间,其实也就是清空单链表内容,同时释放头结点
template<class elemType>
linkList<elemType>::~linkList() {
clear();
delete head;
}
(三) 清空单链表
清空单链表的主要思想就是从头结点开始逐步将后面节点释放掉,但是我们又不想轻易的修改头指针head的指向,所以我们引入一个工作指针,从头结点一直移动到表尾,逐步释放节点
template<class elemType>
void linkList<elemType>::clear() {
Node *p, *tmp;
p - head -> next;
while(p != NULL) {
tmp = p;
p = p -> next();
delete tmp;
}
head -> next = NULL;
tail = head;
curLength = 0;
}
(四) 求表长
由于我们的代码中已经定义过一个叫做 curLength 的变量用来记录我们的表长
所以我们可以直接返回,我们在定义中已经实现了,也就是这句
//返回单链表的当前实际长度
int size()const {return curLength;}
但是如果我们没有这样一个变量,我们想要实现这样的功能又是什么样的方法呢?
template<class elemType>
int linkList<elemType>::size()const {
Node *p = head -> next;
int count;
while(p) {count++; p = p -> next;}
return count;
}
(五) 遍历单链表
我们需要从头到尾访问单链表中的每一个节点,并且输出其中数据域的信息
template<class elemType>
void linkList<elemType>::traverse()const {
Node *p = head -> next;
cout << "traverse:";
while (p != NULL) {
cout << p -> date << " ";
p = p -> next;
}
}
(六) 按照位序 i 寻找其元素对应内存地址
设置一个移动工作指针,和一个计数器 count,初始时p指向头结点,每当指针p移向下一个结点的时候,计数器count + 1 ,直到 p指向位序为 i的节点为止。返回 p
template<class elemType>
typename linkList<elemType>::Node *linkList<elemType>::getPostion(int i)const {
if(i < -1 || i > curLength - 1)
return NULL;
Node *p = head;
int count = 0;
while(count <= i) {
p = p -> next;
count++;
}
return p;
}
(七) 按值查询节点位序
设置一个移动工作指针,和一个计数器 count,从单链表的第一个节点开始,开始于给定的值进行比对,如果相等则查找成功,返回节点的位序,否则继续查询知道单链表结束,查询失败返回 -1
template<class elemType>
int linkList<elemType>::search(const elemType&value)const {
Node *p = head -> next;
int count = 0;
while (p != NULL && p -> data != value) {
p = p -> next;
count++;
}
if (p == NULL) {
return -1;
}else {
return count;
}
}
(八) 插入节点
在位序为 i 出插入值为value 的新节点q,我们需要做的就是找到位序为i - 1 的节点p,让q指针域指向原来p的后继,然后修改p的后继为q即可,说白了也就是修改插入元素位置前后的元素指向关系就可以了
template<class elemType>
void linkList<elemType>::insert(int i,const elemType &value) {
Node *p, *q;
if(i < 0 || i > curLength)
throw outOfRange();
p = getPostion(i - 1);
q = new Node(value,p -> next);
p -> next = q;
if (p == tail) tail = q;
curLength++;
}
(九) 删除节点
能看懂添加节点的方法,理解删除节点也是手到擒来
template<class elemType>
void linkList<elemType>::remove(int i) {
//p是待删节点,pre是其前驱
Node *p, *pre;
if(i < 0 || i > curLength)
throw outOfRange();
pre = getPostion(i - 1);
p = pre -> next;
if (p == tail) {
tail = pre;
pre -> next = NULL;
delete p;
} else {
pre -> next = p -> next;
delete p;
}
}
单链表整表的创建
回顾我们前面认识的顺序表,它其实可以理解为一个数组,我们声明一个类型,同时给定值,初始化其大小,但是单链表就不一样了,它是一种动态组织,它不需要像顺序表一样元素集中,它可以随着实际的情况来动态生成节点,所以也不需要预先分配空间大小和位置
(一) 头插法创建单链表
头插法的意思就是说,每次新增节点全部插在头结点之后,首元结点之前,你可以这样理解,我先来排队,但是后面来了人,他就会排到我的前面去,我们来借助图看一下
我们一次插入元素 123 但实际上输出的是按照321的顺序存储的,也就是说和我们的逻辑顺序是相反的
我们来看一看怎么实现它
template<class elemType>
void linkList<elemType>::headCreate() {
Node *p;
elemType value, flag;
cout << "inputelements, ended with:";
cin >> flag;
while(cin >> value, value != flag) {
//p -> data == value, p -> next = head ->next
p = new Node(value, head -> next);
head -> next = p;
//原链表为空,新节点p成为为节点
if (head == tail)
tail = p;
curLength++;
}
}
逆置单链表
我们知道单链表中元素顺序与读入的顺序是相反的,我们可以通过逆置单链表的算法,帮助我们重新恢复我们的惯有思维顺序
template<class elemType>
void linkList<elemType>::inverse() {
Node *p, *tmp;
//p为工作指针,指向首元结点
p = head -> next;
//头结点的指针域置空,构成空链表
head -> next = NULL;
//逆置后首元结点将成为尾节点
if (p)
tail = p;
while (p) {
//暂存p的后继
tmp = p -> next;
p -> next = head -> next;
//节点p插在头结点的后面
head -> next = p;
//继续处理下一个节点
p = tmp;
}
}
(二) 尾插法创建单链表
看完了头插法,但是感觉这样的顺序与我们一贯的思维总是有一点别扭,而尾插法则是一种,逻辑顺序与我们一致的创建方法
还是看一下图
template<class elemType>
void linkList<elemType>::tailCreate() {
Node *p;
elemType value, flag;
cout << "inputelements, ended with:";
cin >> flag;
while(cin >> value, value != flag) {
p = new Node(value,NULL);
tail -> next = p;
tail = p;
curLength++;
}
}
合并单链表
要求:假设我们给出两个仍然是递增的单链表la和lb,我们将其合并为lc 仍保证递增,利用原表空间,但是我们仍在下面将表C称作新表
因为我们的要求是递增的,所以使用尾插法是非常合适的,我们设计三个工作指针,分别指向两个表的首元结点,然后将第三个指针指向新表的头结点,比较前两个指针指向的值,小的就放到新表的表尾,然后后移动两表中较小的那一个的指针,以此类推,直到其中一个表尾空,将剩余的节点全部链接到新表的末尾
template<class elemType>
typename linkList<elemType> *linkList<elemType> ::Union(linkList<elemType> *lb) {
Node *pa, *pb, *pc;
linkList<elemType> *lc = this;
pa = head -> next;
head -> next = NULL;
pb = (lb -> head) -> next;
(lb -> head) -> next = NULL;
pc = lc -> head;
while(pa && pb) {
if(pa -> data <= pb -> data) {
pc-> next = pa;
pc = pa;
pa = pa -> next;
} else {
pc -> next = pb;
pc = pb;
pb = pb -> next;
}
}
if(pa) {
pc -> next = pa;
lc -> tail = tail;
} else {
pc -> next = pb;
lc -> tail = lb -> tail;
}
lc -> cuirLength = curLength + lb -> curLength;
delete lb;
return lc;
}
总结
单链表,采取了链式存储结构,用一组任意的存储单元存放线性表的元素,尤其对于需要频繁的插入和删除数据的时候更加适用,如果需要进行频繁的查找还是推荐使用顺序表,例如对于一个学生成绩管理系统的制作,学生更多的时候是查看自己的成绩,而录入的老师,也只有在考试后录入一次,所以应该使用顺序表,而例如考勤打卡系统,更多的是打卡信息的记录,所以还是选择使用链表,当然例子可能不是很恰当,同时正常的开发中还会有更多复杂的问题需要考虑,举例子只为了利于理解
结尾:
如果文章中有什么不足,或者错误的地方,欢迎大家留言分享想法,感谢朋友们的支持!
如果能帮到你的话,那就来关注我吧!如果您更喜欢微信文章的阅读方式,可以关注我的公众号
在这里的我们素不相识,却都在为了自己的梦而努力 ❤
一个坚持推送原创开发技术文章的公众号:理想二旬不止