线性表
1.特性
2.顺序表
3.链表
4.链表(插入)
5.链表(删除)
6.双向链表
7.双向链表(插入)
8.双向链表(删除)
9.循环链表
10.静态链表
上图的含义:data[ ]中存的是数据,right[ ]中存的是data[ ]中每一个数据的下一个数据的索引。比如data[ ]中第一个数据是空的,下一个数据56的索引是1,则right[ ]中第一个数据就是1。
静态链表的好处:假设一个大小1000的链表,若使用动态链表,寻找一个数据时,总是需要从头开始搜索,比较耗时,若使用静态链表,直接一步到位,按索引就找到该数据。
11.静态链表(插入)
若想在data[ ]链表中,数据49和数据20之间插入数据25:
方法:先将数据25放在data[ ]链表的最后,索引是9,将其插入在49和20中间,即将索引9(数据25)插入在索引5(数据49)和索引6(数据20)之间。
即索引5的下一个节点是9,索引9的下一个节点是6。
则在right[ ]链表中,索引5下面填9,索引9下面填6。
12.静态链表(删除)
若想在data[ ]链表中,删除数据12:
方法:在data[ ]链表中,数据12的索引是3,数据9的索引是2,数据23的索引是4,即直接将数据9和数据23相连,即索引2和索引4相连。
则在right[ ]链表中,在索引2处下面填4。
但不能删除数据12的空间,因为静态链表是一块连续的空间,所以数据12还存在该链表中,但索引已经跳过了它。
13.双向静态链表
14.双向静态链表(插入)
15.双向静态链表(删除)
练习
01
题目:https://www.luogu.com.cn/problem/UVA101
题解:
归位:
移动:
#include <iostream>
#include <vector>
using namespace std;
vector<int> block[30]; //arr数组中有30个元素,每个元素又是一个vector<int>类型的数组
int n; //初始时从左到右有n个木块
//初始化输入的n个容器
void Init()
{
cin >> n;
for (int i = 0; i < n; i++)
{
block[i].push_back(i);//arr[i]表示横向的索引;push_back(i)表示初始时的方块
}
}
void loc(int x, int& p, int& h)//穷举找位置,x表示要找的数,h表示高度,p表示堆数
{
for (int i = 0; i < n; i++)
for (int j = 0; j < block[i].size(); j++)
{
if (block[i][j] == x)
{
p = i;
h = j;
}
}
}
void goback(int p, int h)//p堆 > h的所有块归位
{
for (int i = h + 1; i < block[p].size(); i++)
{
int k = block[p][i];
block[k].push_back(k); //归位
}
block[p].resize(h + 1);//重置大小
}
void moveall(int p, int h, int q)//p堆 >=h 的所有块移动到q之上
{
for (int i = h; i < block[p].size(); i++)
{
int k = block[p][i];
block[q].push_back(k);
}
block[p].resize(h);//重置大小
}
//接收指令
void solve()
{
int a, b;
string s1, s2;
while (cin >> s1)
{
if (s1 == "quit")
break;
cin >> a >> s2 >> b;
int ap = 0, ah = 0, bp = 0, bh = 0;
loc(a, ap, ah); //找到a位置
loc(b, bp, bh); //找到b位置
if (ap == bp) //如果有非法指令(如 aa 与 bb 在同一堆),无需处理
continue;
if (s1 == "move")//a归位
goback(ap, ah);
if (s2 == "onto")//b归位
goback(bp, bh);
moveall(ap, ah, bp);
}
}
void print()
{
for (int i = 0; i < n; i++)
{
cout << i << ":";
for (int j = 0; j < block[i].size(); j++)
cout << " " << block[i][j];
cout << endl;
}
}
int main()
{
Init(); // 初始化
solve(); // 输入指令
print(); //打印结果
return 0;
}
02
题目:https://www.luogu.com.cn/problem/UVA11988
题解:输入:This_is_a_[Beiju]_text
当打字This_is_a_之后,遇到 '[',说明按下了home键,即光标跑到行首,
在行首输入Beiju,此时悲剧文本为BeijuThis_is_a_,又遇到 ']',说明按下
了end键,即光标跑到行尾,在行尾接着输入_text,此时悲剧文本为BeijuThis_is_a__text。
eg.
#include <iostream>
#include <list>
//list将元素按顺序储存在链表中. 与 向量(vectors)相比, 它允许快速的插入和删除,但是随机访问却比较慢
//https://blog.csdn.net/wuruiaoxue/article/details/48159973
//https://www.cnblogs.com/denggelin/p/5745213.html
using namespace std;
void solve(string s)
{
int len = s.size();
list<char> text;
list<char> ::iterator it = text.begin();
for (int i = 0; i < len; i++)
{
if (s[i] == '[') {
it = text.begin();
}
else if (s[i] == ']') {
it = text.end();
}
else {
it = text.insert(it, s[i]);
it++;
}
}
for (it = text.begin(); it != text.end(); it++)
{
cout << *it;
}
s.clear();
cout << endl;
}
int main(void)
{
string s;
while (cin >> s)
{
solve(s);
}
return 0;
}
03
题目:https://www.luogu.com.cn/problem/UVA12657
题解:
程序思路:如果元素个数很大的话,不要真的进行反转,否则时间复杂度很高。所以对于题目中的指令4,将整条线反转,可以进行转化:对于指令1,进行反转后,就变成了指令2;对于指令2,进行反转后,就变成了指令1。
#include <iostream>
using namespace std;
int left_array[100000], right_array[100000]; //声明前驱、后继数组
//初始化双向静态链表,根据笔记双向链表的部分,前驱、后继数组
void initDoubleLink(int num)
{
//前驱left数组索引全部减1,补上一个left[0] = n,后继right数组索引全部加1,
//因为原数组索引是1至n, 加1后变为2至n+1,所以补上一个0和1,即right[num] = 0, right[0] = 1
for (int i = 1; i <= num; i++)
{
left_array[i] = i - 1;
right_array[i] = i + 1;
}
left_array[0] = num;
right_array[num] = 0;
right_array[0] = 1;
}
//链接的操作
//比如1,2,3;1表示L,3表示R。若删除2,则将1和2链接,即L的后继是R,R的前驱是L。
void link(int L, int R)
{
left_array[R] = L;
right_array[L] = R;
}
int main(void)
{
int k = 0; //输出格式Case 1,Case 2...
int n_num, m_order; //盒子个数,指令个数
int index_order, x_box, y_box; //指令类型,盒子编号x,盒子编号y
bool overturn_flag; //是否反转标识位
while (cin >> n_num >> m_order)
{
overturn_flag = false;
initDoubleLink(n_num);
for (int i = 0; i < m_order; i++)
{
cin >> index_order;
if (index_order == 4)
{
overturn_flag = !overturn_flag;//反转标识位翻转
}
else
{
cin >> x_box >> y_box;
//交换盒子编号X与盒子编号Y的位置,考虑X在Y的右边
//如果指令类型是3,且Y的后继是X,则直接交换Y和X的值
if (index_order == 3 && right_array[y_box] == x_box)
swap(x_box, y_box);
//上面已经对指令类型3和4进行了处理,这里只剩下了1和2指令,
//若反转标识也为true,则指令1将X放到Y左边经过反转后变为将X放到Y右边,即指令1变成了指令2
if (index_order != 3 && overturn_flag)
index_order = 3 - index_order;
//如果X已经在Y的左边了就忽略
if (index_order == 1 && x_box == left_array[y_box])
continue;
//如果X已经在Y的右边了就忽略
if (index_order == 2 && x_box == right_array[y_box])
continue;
//指令1和指令2的操作
int L_x_box = left_array[x_box], R_x_box = right_array[x_box]; //盒子编号x的前驱和后继
int L_y_box = left_array[y_box], R_y_box = right_array[y_box]; //盒子编号x的前驱和后继
if (index_order == 1)
{
//移动盒子编号X到盒子编号Y的左边
link(L_x_box, R_x_box); //将X的前驱和后继直接相链接,删除X
//Y的前驱与X相链接,X与Y相链接,完成X移动到Y的左边
link(L_y_box, x_box);
link(x_box, y_box);
}
else if (index_order == 2)
{
//移动盒子编号X到盒子编号Y的右边
link(L_x_box, R_x_box);
link(y_box, x_box);
link(x_box, R_y_box);
}
else if (index_order == 3) //交换盒子编号X与盒子编号Y的位置,保证X在Y的左边
{
//如果X和Y靠一起,X的后继是Y
if (right_array[x_box] == y_box)
{
link(L_x_box, y_box);
link(y_box, x_box);
link(x_box, R_y_box);
}
else //如果X和Y不靠在一起
{
link(L_x_box, y_box);
link(y_box, R_x_box);
link(L_y_box, x_box);
link(x_box, R_y_box);
}
}
}
}
//对于每组数据,输出他们奇数位置的编号的和
//若盒子的总数是奇数,比如1,2,3,4,5,6,7;可以发现无论是正向还是反转,1357、7531,奇数位置的编号和都是相等的
//若盒子的总数是偶数,比如1,2,3,4,5,6;若是正向,则是奇数位置编号的和(135),若是反转的,奇数位置编号的和(642) = 总和 - 正向的奇数编号和(135)
long long odd_sum = 0; //奇数总和
int t = 0;
//若盒子总个数为奇数
for (int i = 1; i <= n_num; i++)
{
t = right_array[t];
if (i % 2 == 1)
odd_sum += t;
}
//若盒子总个数为偶数
if (overturn_flag && n_num % 2 == 0)
{
odd_sum = (long long)n_num * (n_num + 1) / 2 - odd_sum;
}
cout << "Case " << ++k << ":" << odd_sum << endl;
}
return 0;
}