知识点
线性表的定义
- 线性结构
- 除了 A 0 A_0 A0和 A n − 1 A_{n-1} An−1外,每个元素都有唯一的前趋和后继
- 对于每个 A i A_i Ai,它的前驱是 A i − 1 A_{i-1} Ai−1,它的后继是 A i + 1 A_{i+1} Ai+1
- A 0 A_0 A0只有后继没有前驱, A n − 1 A_{n-1} An−1只有前驱没有后继。
- 线性表:处理线性结构的数据结构
- 线性表的操作
- 创建一个线性表
create()
:创建一个空线性表 - 清除一个线性表
clear()
:删除线性表中的所有数据元素 - 求线性表的长度
length()
:返回线性表的长度 - 在第i个位置插入一个元素
insert(i,x)
:在i
的位置插入元素x
,a[i]
及之后的元素依次后移 - 删除第
i
个位置的元素remove(i)
:删除i位置的元素,a[i+1]
及之后的元素依次前移 - 搜索某个元素在线性表中是否出现
search(x)
:搜索x
并返回其位置 - 访问线性表的第
i
个元素visit(i)
:返回位置i
的元素值 - 遍历线性表运算
traverse()
:按顺序返回每一个元素
- 创建一个线性表
- 线性表的抽象类
template <class elemType> // 定义一个模板类
class list
{
public:
virtual void clear() = 0;
virtual int length() const = 0;
virtual void insert(int i,const elemType 0;
virtual void remove(int i) = 0;
virtual int search(const elemType &x) const = 0;
virtual elemType visit(int i) const = 0;
virtual void traverse() const = 0;
virtual ~list() {}; // 虚析构函数:解决父类指针指向子类对象时,释放子类对象的资源时,释放不完全,造成的内存泄漏问题。
}
顺序表
概念及类定义
顺序表指的是线性表的顺序存储,也就是说线性表中结点存放在存储器上一块连续的空间中,即一个数组。
- 数据成员
- 表元素类型的指针
data
- 数组规模
maxSize
- 数组中的元素个数
currentLength
- 表元素类型的指针
顺序表类定义
template <class elemType>
class seqList: public list<elemType> {
private:
elemType *data;
int currentLength;
int maxSize;
// 为了解决插入元素时,数组容量不够的问题
void doubleSpace();
public:
seqList( int initSize = 10 );
// 析构函数:还掉动态空间
~seqList( ) { delete [] data; }
// 清空数组元素:只要把表长设为0
void clear( ) { currentLength = 0; }
// 数组长度:只需要返回这个私有成员变量currentLength
int length( ) const { return currentLength; }
void insert( int i, const elemType
void remove( int i );
int search( const elemType
// 访问数组元素:返回下标为i的数组元素
elemType visit( int i) const { return data[i] ; }
void traverse( ) const ;
};
顺序表成员函数的实现
- 构造函数:申请一个空表
template <class elemType>
seqList<elemType>::seqList(int initSize) {
data = new elemType[initSize];
maxSize = initSize;
currentLength = 0;
}
- 搜索函数:用
for
循环进行查找
template <class elemType>
int seqList<elemType>::search (const elemType &x) const {
int i;
// 空循环,不满足循环条件时跳出
for (i = 0; i < currentLength && data[i] != x; ++i) ;
if (i == currentLength)
return -1;
else return i;
}
- 遍历函数
template <class elemType>
void seqList<elemType>::traverse() const {
cout << endl;
for (int i = 0; i < currentLength; ++i)
cout << data[i] << ' ';
}
- 插入函数
template <class elemType>
void seqList<elemType>::insert(int i, const elemType &x) {
// 如果数组放满了,先扩大数组空间
if (currentLength == maxSize)
doubleSpace();
// 从后往前,元素后移
for ( int j = currentLength; j > i; j--)
data[j] = data[j-1];
// 空出空间,插入元素,表长加1
data[i] = x;
++currentLength;
}
doubleSpace
函数:扩大数组空间
template <class elemType>
void seqList<elemType>::doubleSpace() {
// 保存指向原来空间的指针
elemType *tmp = data;
// 重新申请空间
maxSize *= 2;
data = new elemType[maxSize];
// 拷贝原有数据
for (int i = 0; i < currentLength; ++i)
data[i] = tmp[i];
// 清除原来申请的空间
delete [] tmp;
}
- 删除函数:
template <class elemType>
void seqList<elemType>::remove(int i) {
// 后面所有元素前移,表长减1
for (int j = i; j < currentLength - 1; j++)
data[j] = data[j+1] ;
--currentLength;
}
总结
- 由于要保持逻辑次序和物理次序的一致性,顺序表在插入删除时需要移动大量的数据,性能不太理想;
- 由于逻辑次序和物理次序的一致性使得定位访问的性能很好;
- 顺序表比较适合静态的、经常做线性访问的线性表。
练习
序列操作1
现有一个空的整数序列,需要你对其进行如下操作:
- 若命令为1 x,表示需要在这个序列末尾新插入整数x。
- 若命令为2 x,表示需要删除从前向后第x个数字,数据保证x一定小于等于当前序列中整数的个数。
- 若命令为3 x,表示需要输出从前向后第x个数字,数据保证x一定小于等于当前序列中整数的个数。
输入描述:
第一行一个整数m,表示操作的次数。数据保证m ≤ 10^3。
接下来m行,每行两个整数,第一个数字表示该次操作的类型,第二个数字x表示该此操作的参数,具体如题目所表述。
输出描述:
一行,若干个整数,为命令3输出的数字,用空格隔开。
示例 1:
输入:
6
1 1
1 2
1 3
2 2
3 1
3 2
输出:
1 3
代码:
#include <iostream>
using namespace std;
int a[1010], cnt;
int main() {
int m, t, x;
cin >> m;
while (m--) {
cin >> t >> x;
if (t == 1)
a[++cnt] = x;
else if (t == 2) {
for (int i = x; i < cnt; i++)
a[i] = a[i + 1];
cnt--;
}
else
cout << a[x] << " ";
}
return 0;
}
单链表
单链表的概念和设计
- 单链表的概念:在单链表中,每个结点由数据元素和指针构成。该指针指向它的直接后继结点,最后一个结点的后继指针为空,即没有后继节点。
- 单链表类的设计:
- 存储设计:需要定义一个结点类,单链表类的数据成员包括头指针和链表长度
currentLength
。 - 工具函数设计:
- 线性表的抽象函数都需要实现;
- 链表的插入、删除操作都需要将指针移到被操作结点的前一结点。通过设计函数
move
实现找到某一个结点的位置的功能。
- 存储设计:需要定义一个结点类,单链表类的数据成员包括头指针和链表长度
单链表类定义
template <class elemType>
class sLinkList: public list<elemType> {
private:
// 定义一个结点的结构体,里面的所有成员都是公有的
struct node {
elemType data;
node *next;
node(const elemType &x, node *n = NULL) { data = x; next = n; }
node( ):next(NULL) { }
~node() {};
};
node *head;
int currentLength;
node *move(int i) const;
public:
// 构造函数:创建空的单链表
sLinkList() {
head = new node;
currentLength = 0;
}
// 析构函数:调用clear把单链表的所有结点都还掉,再把头结点还掉
~sLinkList() { clear(); delete head; }
void clear() ;
// 表的长度:返回私有的成员变量
int length() const { return currentLength; }
void insert(int i, const elemType
void remove(int i);
int search(const elemType
elemType visit(int i) const;
void traverse() const ;
};
单链表成员函数实现
clear()
:把单链表变成一个空表。注意需要把所有结点的空间还给系统。
template <class elemType>
void sLinkList<elemType>::clear() {
node *p = head->next, *q;
head->next = NULL;
// 只要p不是空的,把p的下一个结点记下来,删掉p,再把q赋给p
while (p != NULL) {
q = p->next;
delete p;
p=q;
}
currentLength = 0;
}
insert(i,x)
:在第i
个位置插入元素x
。先让指针指向第i-1
个元素,之后执行插入过程,即申请一个存放x的结点,让该结点的后继指针指向结点i,让第i-1
个结点的后继指针指向这个新结点。
template <class elemType>
void sLinkList<elemType>::insert(int i, const elemType &x) {
node *pos;
// 通过move函数找到第i-1个元素的地址
pos = move(i - 1);
pos->next = new node(x, pos->next);
++currentLength;
}
move(i)
: 返回指向第i
个元素的指针。
template <class elemType>
sLinkList<elemType>::node * sLinkList<elemType>::move(int i) const {
node *p = head;
while (i-- >= 0) p = p->next;
return p;
}
remove(i)
:删除第i
个位置的元素。先找到第i-1
个结点,让该结点的后继指针指向第i+1
个结点,释放被删结点空间。
template <class elemType>
void sLinkList<elemType>::remove(int i) {
node *pos, *delp;
// 通过move函数找到第i-1个元素的地址
pos = move(i - 1);
// 找到被删结点的位置,让第i-1个元素的后继指针指向第i+1个结点
delp = pos->next;
pos->next = delp->next;
// 释放被删结点的空间
delete delp;
--currentLength;
}
search(x)
:搜索某个元素在线性表中是否出现。从头指针的后继结点开始往后检查链表的结点直到找到x或查找到表尾。
template <class elemType>
int sLinkList<elemType>::search(const elemType &x) const {
// 指针指向第一个元素A_0
node *p = head->next;
int i = 0;
while (p != NULL && p->data != x) {
p = p->next;
++i;
}
// p为NULL表示找到表尾都没有找到
if (p == NULL)
return -1;
else
return i;
}
visit(i)
:访问线性表的第i
个元素。通过move(i)
找到第i
个结点,返回该结点的数据部分
template <class elemType>
elemType sLinkList<elemType>::visit(int i) const {
return move(i)->data;
}
traverse()
:遍历运算。从头结点的直接后继开始重复:输出当前结点值,将后继结点设为当前结点,直到当前节点为空。
template <class elemType>
void sLinkList<elemType>::traverse() const {
node *p = head->next;
cout << endl;
while (p != NULL) {
cout << p->data << " ";
p=p->next;
}
cout << endl;
}
练习
序列操作2
现有若干个空的整数序列,需要你对其进行如下操作:
- 若命令为1 x y,表示需要在第x个序列末尾新插入整数y。
- 若命令为2 x,表示需要在第x个序列末尾删除一个数字,删除前保证该序列不为空。
- 若命令为3,表示需要查询当前所有序列中的所包含的元素,每个序列单独一行,对于每个序列按照输入的顺序输出。
数据保证命令3总数不超过2条。
输入描述:
第一行两个整数n m,表示序列的的数量和操作的次数。数据保证n ≤ 10^5
m ≤ 5 * 10^5
。
接下来m行,每行表示一条命令,具体如题目所表述。
输出描述:
每次执行命令3输出n行,每行包含若干整数并用空格隔开,若该序列为空,则此行输出none。
示例 1:
输入:
2 7
1 1 3
3
1 1 4
1 2 1
1 2 2
2 1
3
输出:
3
none
3
1 2
代码:
#include <iostream>
#define N 500010
using namespace std;
int head[100010], nxt[N], val[N], out[N], cnt;
int main() {
int n, m, t, x, y;
cin >> n >> m;
while (m--) {
cin >> t;
if (t == 1) {
cin >> x >> y;
val[++cnt] = y;
nxt[cnt] = head[x];
head[x] = cnt;
}
else if (t == 2) {
cin >> x;
head[x] = nxt[head[x]];
}
else {
for (int i = 1; i <= n; i++) {
int oc = 0, pos = head[i];
while (pos) {
out[++oc] = val[pos];
pos = nxt[pos];
}
if (oc == 0)
cout << "none";
while (oc)
cout << out[oc--] << " ";
cout << endl;
}
}
}
return 0;
}
双链表
双链表的概念和设计
- 双链表概念:在连接实现中,如果每个结点既保存直接后继结点的地址,也保存直接前驱结点的地址,则该链表被称为双链表。
- 双链表类的设计:
- 链表类的数据成员:头指针、尾指针,链表长度
currentLength
。 - 必须定义一个结点类。
- 链表类必须实现线性表的所有操作,故链表类从线性表的抽象类继承。
- 链表的插入、删除操作都需要将指针移到被操作结点的前一结点。通过设计函数
move
实现。
- 链表类的数据成员:头指针、尾指针,链表长度
双链表的类定义
template <class elemType>
class dLinkList: public list<elemType> {
private:
struct node {
elemType data;
node *prev, *next;
node(const elemType &x, node *p = NULL, node *n = NULL) { data = x; next = n; prev = p; }
node( ):next(NULL), prev(NULL) {}
~node() {}
};
node *head, *tail;
int currentLength;
node *move(int i) const;
public:
dLinkList();
// 析构函数:先调用clear删除所有元素,再删除头尾结点
~dLinkList() {
clear();
delete head;
delete tail;
}
void clear();
// 返回元素数量
int length() const { return currentLength; }
void insert(int i, const elemType
void remove(int i);
int search(const elemType
elemType visit(int i) const;
void traverse() const;
};
双链表成员函数实现
- 构造函数:创建一个双链表就是创建一个只有头尾结点的链表,其中头结点的前驱为空,尾结点的后继为空。
template <class elemType>
dLinkList<elemType>::dLinkList() {
head = new node;
head->next = tail = new node;
tail->prev = head;
currentLength = 0;
}
- 插入:让指针指向第i个元素,执行插入过程,具体方式基本与单链表一致。
template <class elemType>
void dLinkList<elemType>::insert(int i, const elemType &x) {
node *pos, *tmp;
pos = move(i);
tmp = new node(x, pos->prev, pos);
pos->prev->next = tmp;
pos->prev = tmp;
++currentLength;
}
- 删除:让指针指向第i个元素,执行删除过程,具体方式基本与单链表一致。
template <class elemType>
void dLinkList<elemType>::remove(int i) {
node *pos;
pos = move(i);
pos->prev->next = pos->next;
pos->next->prev = pos->prev
delete pos;
--currentLength;
}
- 双链表其他操作和单链表基本相同。
练习
队列安排
一个学校里老师要将班上N个同学排成一列,同学被编号为1∼N,他采取如下的方法:
- 先将1号同学安排进队列,这时队列中只有他一个人;
- 2−N号同学依次入列,编号为i的同学入列方式为:老师指定编号为i的同学站在编号为1 ~ (i-1)中某位同学(即之前已经入列的同学)的左边或右边;
- 从队列中去掉M (M<N)个同学,其他同学位置顺序不变。
在所有同学按照上述方法队列排列完毕后,老师想知道从左到右所有同学的编号。
输入描述:
第1行为一个正整数N,表示了有N个同学。
第2−N行,第i行包含两个整数k p,其中k为小于i的正整数,p为0或者1。若p为0,则表示将i号同学插入到k号同学的左边,p为1则表示插入到右边。
第N+1行为一个正整数M,表示去掉的同学数目。
接下来M行,每行一个正整数x,表示将x号同学从队列中移去,如果x号同学已经不在队列中则忽略这一条指令。
输出描述:
一行,包含最多N个空格隔开的正整数,表示了队列从左到右所有同学的编号,行末换行且无空格。
示例 1:
输入:
4
1 0
2 1
1 0
2
3
3
输出:
2 4 1
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100005
using namespace std;
struct node {
int l, r;
}nod[N];
int head, tail;
void add(int a, int b, int c) {
nod[a].r = b;
nod[b].r = c;
nod[c].l = b;
nod[b].l = a;
}
void del(int a, int b, int c) {
nod[a].r = c;
nod[c].l = a;
nod[b].r = nod[b].l = 0;
}
int n, m;
int main() {
scanf("%d",
head = n + 1;
tail = n + 2;
nod[head].r = tail;
nod[tail].l = head;
add(head, 1, tail);
for (int i = 2; i <= n; ++i) {
int x, y;
scanf("%d%d",
if (!y) add(nod[x].l, i, x);
else add(x, i, nod[x].r);
}
scanf("%d",
for (int i = 0; i < m; ++i) {
int x;
scanf("%d",
if (!nod[x].l) continue;
del(nod[x].l, x, nod[x].r);
}
for (int i = nod[head].r; i != tail; i = nod[i].r) printf("%d ", i);
return 0;
}
scanf("%d",
head = n + 1;
tail = n + 2;
nod[head].r = tail;
nod[tail].l = head;
add(head, 1, tail);
for (int i = 2; i <= n; ++i) {
int x, y;
scanf("%d%d",
if (!y) add(nod[x].l, i, x);
else add(x, i, nod[x].r);
}
scanf("%d",
for (int i = 0; i < m; ++i) {
int x;
scanf("%d",
if (!nod[x].l) continue;
del(nod[x].l, x, nod[x].r);
}
for (int i = nod[head].r; i != tail; i = nod[i].r) printf("%d ", i);
return 0;
}