目录
7.1 栈的应用
Codeup Contest ID:100000605
PS:因为栈的应用和队列的应用这两节里面的题目和第六章里的stack、queue一模一样,这里就直接放链接了,有需要的可以到前面的章节去看代码。
请见:《算法笔记》学习日记——6.7 stack的常见用法详解
7.2 队列的应用
Codeup Contest ID:100000606
请见:《算法笔记》学习日记——6.5 queue的常见用法详解
7.3 链表处理
问题 A: 算法2-8~2-11:链表的基本操作
题目描述
链表是数据结构中一种最基本的数据结构,它是用链式存储结构实现的线性表。它较顺序表而言在插入和删除时不必移动其后的元素。现在给你一些整数,然后会频繁地插入和删除其中的某些元素,会在其中某些时候让你查找某个元素或者输出当前链表中所有的元素。
下面给你基本的算法描述:
输入
输入数据只有一组,第一行有n+1个整数,第一个整数是这行余下的整数数目n,后面是n个整数。这一行整数是用来初始化列表的,并且输入的顺序与列表中的顺序相反,也就是说如果列表中是1、2、3那么输入的顺序是3、2、1。
第二行有一个整数m,代表下面还有m行。每行有一个字符串,字符串是“get”,“insert”,“delete”,“show”中的一种。如果是“get”或者“delete”,则其后跟着一个整数a,代表获得或者删除第a个元素;如果是“insert”,则其后跟着两个整数a和e,代表在第a个位置前面插入e;“show”之后没有整数。
输出
如果获取成功,则输出该元素;如果删除成功则输出“delete OK”;如果获取失败或者删除失败,则输出“get fail”以及“delete fail”。如果插入成功则输出“insert OK”,否则输出“insert fail”。如果是“show”则输出列表中的所有元素,如果列表是空的,则输出“Link list is empty”。注:所有的双引号均不输出。
样例输入
3 3 2 1
21
show
delete 1
show
delete 2
show
delete 1
show
delete 2
insert 2 5
show
insert 1 5
show
insert 1 7
show
insert 2 5
show
insert 3 6
show
insert 1 8
show
get 2
样例输出
1 2 3
delete OK
2 3
delete OK
2
delete OK
Link list is empty
delete fail
insert fail
Link list is empty
insert OK
5
insert OK
7 5
insert OK
7 5 5
insert OK
7 5 6 5
insert OK
8 7 5 6 5
7
提示
提示:
1、因为输入数据中含有大量的插入和删除操作(不管你信不信,反正我信了),所以必须使用链表,否则很可能会超时。这也是考查链表的特性吧。
2、初始化链表的元素是倒序的,这个使用题目中创建列表的方法(从头部插入)就可以了。
总结:
这题考查的是链表的特性。顺序表中,怎样判断何时使用顺序表何时使用链表呢?就要看它们的特点了。顺序表的特点是随机存取、随机访问,也就是说如果存取和查询比较频繁的话使用顺序表比较合适;链表的特点是插入和删除时不必移动其后的节点,如果插入和删除操作比较频繁的话使用链表比较合适。
思路
一开始想用STL的forward_list(单向链表)偷懒,结果好不容易把四个方向处理好,提交代码结果时间超限了……(我估计是while(m–)和用for循环处理字符串这两重循环的问题造成的时间超限,然而后来事实证明并不是,我改成输入整型数字之后还是超时了,说明插入和删除操作是真的多呀……)。
然后用题目给的函数做了一遍,就AC了。所以能得出的结论就是,STL的链表其实蛮慢的,要是题目不是很复杂的话,建议自己写。我一开始也不理解为什么《算法笔记》这本书上对于list这个容器不做介绍,现在明白了……
代码
- 用STL容器的forward_list(超时了):
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<forward_list>
#include<string>
#include<iostream>
using namespace std;
int main(){
int n;
while(cin>>n){
forward_list<int> L;//单向链表
while(n--){
int tmp;
cin>>tmp;
L.push_front(tmp);//往前插入元素
}
int m;
cin>>m;
while(m--){
string tmp;
cin>>tmp;
if(tmp=="show"){//show
forward_list<int>::iterator it;
if(L.empty()==true) cout<<"Link list is empty";
else{
for(it=L.begin();it!=L.end();it++){
if(it==L.begin()) cout<<*it;
else cout<<" "<<*it;
}
}
cout<<endl;
}
if(tmp=="delete"){//delete
forward_list<int>::iterator it;
string number;
int pos;
cin>>pos;
if(L.empty()==true) cout<<"delete fail"<<endl;
else if(pos==1&&L.empty()==false){
L.pop_front();//删除第一个元素
cout<<"delete OK"<<endl;
}
else{
int cnt = 0;
for(it=L.begin();it!=L.end();it++){
cnt++;
if(cnt==pos-1) break;//如果是pos前一个位置,就break
}
if(cnt==pos-1){//如果该位置存在元素
L.erase_after(it);//删除迭代器it后面的元素
cout<<"delete OK"<<endl;
}
else cout<<"delete fail"<<endl;
}
}
if(tmp=="insert"){//insert
forward_list<int>::iterator it;
string number;
int pos, temp;//pos为插入的位置,temp为插入的元素
cin>>pos>>temp;
int spacei = 0;//记录空格的位置
if(L.empty()==true&&pos!=1) cout<<"insert fail"<<endl;//如果是空链表,但是pos不是1,则插入失败
else if(pos==1){
L.push_front(temp);//在第1个位置插入元素temp
cout<<"insert OK"<<endl;
}
else{
int cnt = 0;
for(it=L.begin();it!=L.end();it++){
cnt++;
if(cnt==pos-1) break;//如果是pos前一个位置,就break
}
if(cnt==pos-1){
L.insert_after(it, temp);//在迭代器it后面的位置插入元素temp
cout<<"insert OK"<<endl;
}
else cout<<"insert fail"<<endl;
}
}
if(tmp=="get"){//如果是get
forward_list<int>::iterator it;
string number;
int pos;
cin>>pos;
int cnt = 0;
for(it=L.begin();it!=L.end();it++){
cnt++;
if(cnt==pos) break;//如果遍历到了当前位置,break掉
}
if(cnt==pos) cout<<*it<<endl;
else cout<<"get fail"<<endl;
}
}
}
return 0;
}
- 用题目给的函数:
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
typedef int ElemType;
typedef struct LNode{
ElemType data;
struct LNode* next;
}LNode, *LinkList;
void GetElem_L(LinkList &L, int i, ElemType &e){
LinkList p;
p = L->next;
int j = 1;
while(p&&j<i){
p = p->next;
++j;
}
if(!p||j>i) printf("get fail\n");
else{
e = p->data;
printf("%d\n", e);
}
}
void ListInsert_L(LinkList &L, int i, ElemType e){
LinkList p, s;
p = L;
int j = 0;
while(p&&j<i-1){
p = p->next;
++j;
}
if(!p||j>i-1) printf("insert fail\n");
else{
s = (LinkList)malloc(sizeof(LNode));
s->data = e;
s->next = p->next;
p->next = s;
printf("insert OK\n");
}
}
void ListDelete_L(LinkList &L, int i, ElemType &e){
LinkList p, q;
p = L;
int j = 0;
while(p->next&&j<i-1){
p = p->next;
++j;
}
if(!(p->next)||j>i-1) printf("delete fail\n");
else{
q = p->next;
p->next = q->next;
e = q->data;
free(q);
printf("delete OK\n");
}
}
void CreateList_L(LinkList &L, int n){
LinkList p;
int i;
L = (LinkList)malloc(sizeof(LNode));
L->next = NULL;
for(i=n;i>0;i--){
p = (LinkList)malloc(sizeof(LNode));
scanf("%d", &p->data);
p->next = L->next;
L->next = p;
}
}
void ShowList_L(LNode* head){
LNode* p=head->next;
if(p==NULL) printf("Link list is empty");
else{
while(p){
if(p==head->next) printf("%d", p->data);
else printf(" %d", p->data);
p = p->next;
}
}
printf("\n");
}
char gett[7]="get";
char insertt[7]="insert";
char deletee[7]="delete";
char showw[7]="show";
int main(){
int n;
while(scanf("%d", &n) != EOF){
LNode* head;
head = (LNode*)malloc(sizeof(LNode));
head->next=NULL;
int e = 0;
CreateList_L(head, n);
int m;
scanf("%d", &m);
while(m--){
char tmp[7]={0};
scanf("%s", tmp);
if(strcmp(tmp, gett)==0){
int pos;
scanf("%d", &pos);
GetElem_L(head, pos, e);
}
if(strcmp(tmp, insertt)==0){
int pos, temp;
scanf("%d%d", &pos, &temp);
ListInsert_L(head, pos, temp);
}
if(strcmp(tmp, deletee)==0){
int pos;
scanf("%d", &pos);
ListDelete_L(head, pos, e);
}
if(strcmp(tmp, showw)==0){
ShowList_L(head);
}
}
}
return 0;
}
问题 B: C语言-链表排序
题目描述
已有a、b两个链表,每个链表中的结点包括学号、成绩。要求把两个链表合并,按学号升序排列。
输入
第一行,a、b两个链表元素的数量N、M,用空格隔开。 接下来N行是a的数据 然后M行是b的数据 每行数据由学号和成绩两部分组成
输出
按照学号升序排列的数据
样例输入
2 3
5 100
6 89
3 82
4 95
2 10
样例输出
2 10
3 82
4 95
5 100
6 89
思路
这题本来想用静态链表的来着……结果题目根本没有输入地址,那么直接用结构体数组+sort函数就能完成了。
代码
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
const int maxn = 100010;
struct node{
int number, score;
}nodes[maxn];
bool cmp(node a, node b){
return a.number<b.number;
}
int main(){
int n, m;
while(scanf("%d%d", &n, &m) != EOF){
for(int i=0;i<n;i++) scanf("%d%d", &nodes[i].number, &nodes[i].score);
for(int i=n;i<m+n;i++) scanf("%d%d", &nodes[i].number, &nodes[i].score);
sort(nodes, nodes+m+n, cmp);
for(int i=0;i<(m+n);i++) printf("%d %d\n", nodes[i].number, nodes[i].score);
memset(nodes, 0, sizeof(nodes));
}
return 0;
}
问题 C: 最快合并链表(线性表)
题目描述
知L1、L2分别为两循环单链表的头结点指针,m,n分别为L1、L2表中数据结点个数。要求设计一算法,用最快速度将两表合并成一个带头结点的循环单链表。
输入
m=5
3 6 1 3 5
n=4.
7 10 8 4
输出
3 6 1 3 5 7 10 8 4
样例输入
7
3 5 1 3 4 6 0
5
5 4 8 9 5
样例输出
3 5 1 3 4 6 0 5 4 8 9 5
思路
这题也很简单,创建两个链表之后,只要把第一条链表的指针域指向第二条链表的第一个结点即可。
代码
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
struct node{
int data;
node* next;
};
node* create(int x){
node *p, *pre, *head;
head = new node;
head->next = NULL;
pre = head;
for(int i=1;i<=x;i++){
p = new node;
scanf("%d", &p->data);
p->next = NULL;
pre->next = p;
pre = p;
}
return head;
}
int main(){
int m, n;
while(scanf("%d", &m) != EOF){
node* L1 = create(m);//第一条链表的头结点L1
node* tmp = L1;//暂存链表L1的头结点
scanf("%d", &n);
node* L2 = create(n);//第二条链表的头结点L2
for(int i=1;i<=m;i++){//使指针移动到最后一个结点
L1 = L1->next;//指针移动到下一个结点
}
L1->next = L2->next;//链表L1的最后一个结点的指针域指向链表L2头结点指向的内容(即都指向链表L2的第一个结点)
tmp = tmp->next;//先移动到第一个结点,不然头结点是没有数据域的
for(int i=1;i<=m+n;i++){
if(i==1) printf("%d", tmp->data);
else printf(" %d", tmp->data);
tmp = tmp->next;
}
printf("\n");
}
return 0;
}
问题 D: 链表查找(线性表)
题目描述
线性表(a1,a2,a3,…,an)中元素递增有序且按顺序存储于计算机内。要求设计一算法完成:
(1) 用最少时间在表中查找数值为x的元素。
(2) 若找到将其与后继元素位置相交换。
(3) 若找不到将其插入表中并使表中元素仍递增有序。
输入
输入:x=3
输入长度:9
输入数据:2 3 5 7 12 15 17 23 45
输出
相同元素为:3
交换后的链表为:2 5 3 7 12 15 17 23 45
样例输入
4
9
2 3 5 7 12 15 17 23 45
样例输出
no
2 3 4 5 7 12 15 17 23 45
思路
这题也是比较基础的,主要考查了链表的查找操作和插入操作,为了操作方便,在这里我建议在遍历查找的时候时刻记录当前结点的前驱结点,否则不管是交换位置还是插入,都会比较麻烦。
思路也很简单,因为序列一定是递增有序的,那么我们要查找x,并不需要遍历整个链表(如果无x元素的话,遍历整个链表显然是浪费时间),因此,只要在L->data≤x这个范围内遍历即可(即:在结点的数据域小于等于x的范围内遍历)。
为了方便判断是否存在x,我还设了一个bool型变量flag,初始化为false,如果找到就设为true,如果循环结束还是false的话,就说明没找到。
代码
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<iostream>
using namespace std;
struct node{
int data;
node* next;
};
node* create(int x){
node *p, *pre, *head;
head = new node;
head->next = NULL;
pre = head;
for(int i=1;i<=x;i++){
p = new node;
scanf("%d", &p->data);
p->next = NULL;
pre->next = p;
pre = p;
}
return head;
}
int main(){
int x, len;
while(scanf("%d", &x) != EOF){
bool flag = false;//记录是否存在x的元素
scanf("%d", &len);
node* L = create(len);
node* head = L;//保存头结点
node* pre = L;//前驱结点初始化为头结点
L = L->next;//移动到第一个结点
while(L->data<=x){//因为一定是递增有序,所以只要遍历结点的数据域小于等于x的那部分结点即可
if(L->data==x){//如果存在
flag = true;
node* aft = L->next;//记录后继结点
pre->next = L->next;
L->next = aft->next;
aft->next = L;
break;
}
pre = L;//把当前的结点L作为前驱
L = L->next;//移动到下一个结点
}
if(flag==false){//如果并未找到x,此时的L是比x大的那个结点,而前驱pre是比x小的那个结点
//因为移动了一次指针之后,指针所指向结点的数据域大于x,才会终止while()循环
printf("no\n");
node* p = new node;
p->data = x;//插入数据域为x的新结点p
p->next = pre->next;
pre->next = p;
}
head = head->next;//移动到第一个结点
if(flag==true){
for(int i=1;i<=len;i++){
if(i==1) printf("%d", head->data);
else printf(" %d", head->data);
head = head->next;
}
}
else{
for(int i=1;i<=len+1;i++){
if(i==1) printf("%d", head->data);
else printf(" %d", head->data);
head = head->next;
}
}
printf("\n");
}
return 0;
}
问题 E: 算法2-24 单链表反转
题目描述
根据一个整数序列构造一个单链表,然后将其反转。
例如:原单链表为 2 3 4 5 ,反转之后为5 4 3 2
输入
输入包括多组测试数据,每组测试数据占一行,第一个为大于等于0的整数n,表示该单链表的长度,后面跟着n个整数,表示链表的每一个元素。整数之间用空格隔开
输出
针对每组测试数据,输出包括两行,分别是反转前和反转后的链表元素,用空格隔开
如果链表为空,则只输出一行,list is empty
样例输入
5 1 2 3 4 5
0
样例输出
1 2 3 4 5
5 4 3 2 1
list is empty
思路
这题我直接用了STL里的forward_list,因为有现成的reverse()函数,反转起来非常方便,缺点是可能用时久一点(64ms)。
需要注意的是,用emplace_front()或者push_front()的时候,构建出来的链表和输入的顺序是相反的,所以先要来个reverse(),才是正序的链表(emplace_after()好像可以直接构建正序链表,但是我不知道为啥一直出错,所以就没用了)。
代码
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<forward_list>
#include<iostream>
using namespace std;
int main(){
int n;
while(scanf("%d", &n) != EOF){
if(n==0) printf("list is empty\n");
else{
forward_list<int> L;
for(int i=1;i<=n;i++){
int tmp;
scanf("%d", &tmp);
L.emplace_front(tmp);//逆序创建链表
}
L.reverse();//正序
for(forward_list<int>::iterator it=L.begin();it!=L.end();it++){
if(it==L.begin()) printf("%d", *it);
else printf(" %d", *it);
}
printf("\n");
L.reverse();//逆序
for(forward_list<int>::iterator it=L.begin();it!=L.end();it++){
if(it==L.begin()) printf("%d", *it);
else printf(" %d", *it);
}
printf("\n");
}
}
return 0;
}
问题 F: 算法2-25 有序单链表删除重复元素
题目描述
根据一个递增的整数序列构造有序单链表,删除其中的重复元素
输入
输入包括多组测试数据,每组测试数据占一行,第一个为大于等于0的整数n,表示该单链表的长度,后面跟着n个整数,表示链表的每一个元素。整数之间用空格隔开
输出
针对每组测试数据,输出包括两行,分别是删除前和删除后的链表元素,用空格隔开
如果链表为空,则只输出一行,list is empty
样例输入
5 1 2 3 4 5
5 1 1 2 2 3
0
样例输出
1 2 3 4 5
1 2 3 4 5
1 1 2 2 3
1 2 3
list is empty
思路
同样的,这题我也用了STL容器里的forward_list,因为里面的unique()函数可以直接去重,使用起来极其方便,需要注意的是,unique()函数里面是可以填参数的,也就是说,可以根据自己的需求来删去一些元素,具体样例可以看C++ Reference里的相关内容:
std::forward_list::unique
代码
#include<cstdio>
#include<string.h>
#include<algorithm>
#include<string>
#include<forward_list>
#include<iostream>
using namespace std;
int main(){
int n;
while(scanf("%d", &n) != EOF){
if(n==0) printf("list is empty\n");
else{
forward_list<int> L;
for(int i=1;i<=n;i++){
int tmp;
scanf("%d", &tmp);
L.emplace_front(tmp);
}
L.reverse();//正序
for(forward_list<int>::iterator it=L.begin();it!=L.end();it++){
if(it==L.begin()) printf("%d", *it);
else printf(" %d", *it);
}
printf("\n");
L.unique();//去重
for(forward_list<int>::iterator it=L.begin();it!=L.end();it++){
if(it==L.begin()) printf("%d", *it);
else printf(" %d", *it);
}
printf("\n");
}
}
return 0;
}
小结
总的来说,链表非常适合插入和删除操作,但是缺点是不支持随机访问,因为毕竟它所有的元素都凌乱地分布在内存中,不像数组一样是连续的内存空间,因此,想要频繁地访问某个元素的话,还是建议用数组比较好,因为链表只能从头遍历过去,时间复杂度显然比数组下标访问要高许多。
当然,对于插入和删除的操作,链表就明显优于数组了,如果实在记不住链表的动态实现方法,就用STL的forward_list(单向链表)或者list(双向链表)吧~