一、顺序表
顺序表中元素的存储是连续的,中间不允许有空。
根据空间分配方式有静态分配和动态分配。
1.定义
(1)数据结构静态定义
#define maxSize 100
typedef struct sqlist{
int data[maxSize];
int length;
}sqList;
(2)数据结构动态定义
#define maxSize 100
typedef struct sqlist{
int *elem;//定义基地址
int length;
}sqList;
2.初始化
bool initList(sqList &L){
L.elem=new int[maxSize];
if(!L.elem) return false;
L.length=0;
return true;
}
3.取值
(1)判断i的合法性
(2)e=L.elem[i-1]
4.插入
在第i个位置之前插入一个元素e(将元素插入在第i个位置)
(1)判断i的合法性(1≤i≤L.length+1),保证连续性。
(2)移动(要移动n-i+1个元素,将下标i-1到n-1的元素向后移动)
for(int j=L.length-1;j>=i-1;j--)
L.elem[j+1]=L.elem[j];
L.length++;
平均时间复杂度为
5.删除
(1)判断i的合法性1≤i≤L.length
(2)移动
for(int j=i+1;i<L.length;j++)
L.elem[j-1]=L.elem[j];
L.length--;
平均时间复杂度为
二、单链表
1.数据结构定义
typedef struct Lnode{
int data;
struct Lnode *next;
}Lnode,*LinkList;
2.链表创建
头插法
每次把新结点插入到头结点之后,是逆序的。
s->next=L->next;
L-next=s;
先修改没有指针标记的那一端
尾插法
每次把新结点链接到链表的尾部,是顺序的。
需要尾指针r来记录最后一个结点,尾指针在不断移动。
s->next=null;
r->next=s;
r=s;
3.取值和查找
链表的头指针不可以随意改动。
4.插入和删除
在第i个结点之前插入一个元素,必须先找到第i-1个结点。
要删除第i个结点,先找到第i-1个结点。free(s)
5.单链表就地逆置
将带有头结点的单链表就地逆置,辅助空间复杂度为O(1)
p=L->next;//第一个节点
L->next=NULL;
q=p->next;//第二个节点
while(q){ //如果p是最后一个节点,q为NULL,遇到q=q->next;会报错
//头插法插入p节点
p->next=L->next;
L->next=p;
//向后挪动
p=q;
q=q->next;
}
//处理最后一个结点
p->next=L->next;
L->next = p;
6.全部代码
#include <bits/stdc++.h>
#define ll long long
#define INF 0x3f3f3f3f
using namespace std;
typedef struct Lnode{
int data;//数据域
struct Lnode *next;//指针域
}Lnode,*LinkList;
//初始化链表
bool InitList_L(LinkList &L){
L=new Lnode;
if(!L){
return false;
}
L->next=NULL;
return true;
}
//头插法创建链表
void creatList_H(LinkList &L){
int n;
LinkList s;
L=new Lnode;
L->next=NULL;
cout << "请输入元素个数n:"<<endl;
cin >>n;
cout <<"请依次输入n个元素:"<<endl;
cout <<"头插法创建单链表"<<endl;
while(n--){
s=new Lnode;
cin >>s->data;
s->next=L->next;
L->next=s;
}
}
//尾插法创建链表
void creatList_R(LinkList &L){
int n;
LinkList s,r;
L=new Lnode;
L->next=NULL;
r=L;
cout << "请输入元素个数n:"<<endl;
cin >>n;
cout <<"请依次输入n个元素:"<<endl;
cout <<"头插法创建单链表"<<endl;
while(n--){
s=new Lnode;
cin >>s->data;
s->next=NULL;
r->next=s;
r=s;
}
}
//取值
bool GetElem_L(LinkList L,int i,int &e){
LinkList p;
p=L->next;//p指向第一个节点
int j=1;
while(p&&j<i){//向后扫描,直到p指向第i个元素或p为空
p=p->next;
j++;
}
if(!p||i<j)//判断i值是否合法:i>n||i<1
return false;
e=p->data;
return true;
}
//查找
bool LocateElem_L(LinkList L,int e){
LinkList p;
p=L->next;
while(p&&p->data!=e){//向后扫描,直到p为空或p所指结点的数据域=e
p=p->next;
}
if(!p)
return false;
return true;
}
//插入
bool ListInsert_L(LinkList &L,int i,int e){
LinkList p,s;
p=L;
int j=0;
while(p&&j<(i-1)){//查找第i-1个结点,p指向该节点
p=p->next;
j++;
}
if(!p||i<(j+1))//判断i是否合法:i>n+1||i<1
return false;
s=new Lnode;
s->data=e;
s->next=p->next;
p->next=s;
return true;
}
//删除
bool ListDelet_L(LinkList &L,int i){
LinkList p,q;
p=L;
int j=0;
while(p->next&&j<(i-1)){//查找第i-1个结点,p指向该节点
p=p->next;
j++;
}
if(!(p->next)||i<(j+1))//判断i是否合法:i>n||i<1(第i-1个结点不能为最后一个结点)
return false;
q=p->next;
p->next=q->next;
delete q;
return true;
}
void Listprint_L(LinkList L){
LinkList p;
p=L->next;
while(p){
cout<<p->data<<"\t";
p=p->next;
}
cout<<endl;
}
int main()
{
int i,x,e,choose;
LinkList L;
cout<<"1.初始化\n";
cout<<"2.创建单链表(前插法)\n";
cout<<"3.创建单链表(尾插法)\n";
cout<<"4.取值\n";
cout<<"5.查找\n";
cout<<"6.插入\n";
cout<<"7.删除\n";
cout<<"8.输出\n";
cout<<"0.退出\n";
choose=-1;
while(choose!=0){
cout<<"请输入数字选择:";
cin>>choose;
switch(choose){
case 1:
if(InitList_L(L))
cout<<"初始化一个空的单链表!\n";
break;
case 2:
creatList_H(L);
cout<<"头插法创建单链表输出结果:\n";
Listprint_L(L);
break;
case 3:
creatList_R(L);
cout<<"尾插法创建单链表输出结果:\n";
Listprint_L(L);
break;
case 4:
cout<<"请输入一个位置i用来取值:";
cin>>i;
if(GetElem_L(L,i,e)){
cout<<"查找成功\n";
cout<<"第"<<i<<"个元素是:"<<e<<endl;
}
else
cout<<"查找失败\n\n";
break;
case 5:
cout<<"请输入要查找元素x:";
cin>>x;
if(LocateElem_L(L,x))
cout<<"查找成功\n";
else
cout<<"查找失败!"<<endl;
break;
case 6:
cout<<"请输入要插入的位置和元素(用空格隔开):";
cin>>i;
cin>>x;
if(ListInsert_L(L,i,x))
cout<<"插入成功\n";
else
cout<<"插入失败!"<<endl;
break;
case 7:
cout<<"请输入要删除的元素位置i:";
cin>>i;
if(ListDelet_L(L,i))
cout<<"删除成功\n";
else
cout<<"删除失败!"<<endl;
break;
case 8:
Listprint_L(L);
break;
}
}
return 0;
}
三、题目
1.Broken Keyboard (a.k.a. Beiju Text)
你在用坏了的键盘打一段很长的文字。有时“home”键(将光标定位到一行的开头)或“end”键(将光标定位到一行的结尾)被自动按下(内部)。你没有意识到这个问题,因为你专注于文本,甚至没有打开显示器!输入完成后,您可以在屏幕上看到文本(悲剧文本)。你的任务是找到悲剧文本。
这里有几个测试用例。每个测试用例是单行,包含至少一个,最多100,000个字母、下划线和两个特殊字符'['和']'。“[”表示按下“Home”键'] '表示按下'结束'键。输入以end- file结束(EOF)。
显而易见,这道题要使用单链表,因为有频繁的插入工作。如果用顺序表,遇到“[”光标定位到开头时 ,后序的插入工作需要将元素整体往后移动一位。
#include<iostream>
#include<cstdio>
#include <cstring>
using namespace std;
typedef struct Lnode{
char data;
struct Lnode *next;
}Lnode,*LinkList;
//初始化链表
void InitList_L(LinkList &L){
L=new Lnode;
L->next=NULL;
}
//删除整个单链表
void deleteList(LinkList &L){
LinkList p;
while(L->next)
{
p=L->next;
L->next=p->next;
delete p;
}
}
//打印
void Listprint_L(LinkList L){
LinkList p;
p=L->next;
while(p){
cout<<p->data<<"";
p=p->next;
}
cout<<endl;
}
int main()
{
LinkList L,r,p;
char c;
InitList_L(L);
r=p=L;
while(scanf("%c", &c) != EOF) {
if(c== '\n'){
Listprint_L(L);
deleteList(L);
r=p=L;
}
else{
if(c=='[') p=L;//光标定位到开头
else if(c==']') p=r;//光标定位到结尾
else{
LinkList s;
s=new Lnode;
s->data=c;
s->next=p->next;
p->next=s;
p=s;
if(p->next==NULL) r=p;//L保持不动,但r随着尾部的插入工作会变
}
}
}
return 0;
}
2.The Blocks Problem
有n(0<n<25)快block,有5种操作:
move a onto b:把a和b上方的木块全部归位,然后把a摞在b上面。
move a over b:把a上方的木块全部归位,然后把a放在b所在木块堆的顶部。
pile a onto b:把b及上面的木块整体摞在b所偶在木块堆的顶部,
pile a over b:把a及上面的木块整体摞在b所在木块堆的顶部。
quit结束命令,前四个动作中若a = b或ab在同一堆中,则不做改变。
方法一:vector
用vector不定长数组存放摞在木块上方的木块。这四条命令都是将a及上面的木块移动到b(a上面的木块归位后,也可以看作将a及上面的木块移动到b,只是上面的木块数量为0),所以区别在于归位。
如果写四个命令判断的话,就有点繁琐,所以我们来看看这四个命令之间归位的联系。可以发现move命令归位a,over命令归位b。
#include<iostream>
#include <vector>
#include<cstdio>
#include <cstring>
using namespace std;
const int maxn = 25;
int n;
vector<int> pile[maxn];//数组,数组中每个元素是一个 vector<int> 类型的向量
void find_block(int &row,int &col,int value){
for(int i=0;i<n;i++){
for(int j=0;j<pile[i].size();j++){
if(pile[i][j]==value){
row=i;
col=j;
return;
}
}
}
}
void goback(int ,int row,int col){
int num = 0;
for(int i=col+1;i<pile[row].size();i++){
num = pile[row][i];
pile[num].push_back(num);
}
pile[row].resize(col+1);
}
void print_elements(){
for(int i=0;i<n;i++){
printf("%d:",i);
for(int j=0;j<pile[i].size();j++){
printf(" %d",pile[i][j]);
}
printf("\n");
}
}
int main()
{
int a,b,row,col,row1,col1;
string s1,s2;
scanf("%d",&n);
for(int i=0;i<n;i++){
pile[i].push_back(i);
}
while(cin>>s1){
if(s1=="quit"){
print_elements();
break;
}
cin>>a>>s2>>b;
find_block(row,col,a);
find_block(row1,col1,b);
if(a!=b&&row!=row1){
//归位
if(s1=="move")goback(a,row,col);
if(s2=="onto")goback(b,row1,col1);
//移动
for(int i=col;i<pile[row].size();i++){
pile[row1].push_back(pile[row][i]);
}
pile[row].resize(col);
}
}
return 0;
}
方法二:顺序表
用动态定义的顺序表替换vector就行了。
#include<iostream>
#include <vector>
#include<cstdio>
#include <cstring>
using namespace std;
const int maxn = 25;
typedef struct sqlist{
int *elem;//定义基地址
int length;
}sqList;
sqList pile[maxn];
int n;
void find_block(int &row,int &col,int value){
for(int i=0;i<n;i++){
for(int j=0;j<pile[i].length;j++){
if(pile[i].elem[j]==value){
row=i;
col=j;
return;
}
}
}
}
void goback(int ,int row,int col){
int num = 0;
for(int i=col+1;i<pile[row].length;i++){
num = pile[row].elem[i];
pile[num].elem[pile[num].length]=num;
pile[num].length++;
}
pile[row].length=col+1;
}
void print_elements(){
for(int i=0;i<n;i++){
printf("%d:",i);
for(int j=0;j<pile[i].length;j++){
printf(" %d",pile[i].elem[j]);
}
printf("\n");
}
}
int main()
{
int a,b,row,col,row1,col1;
string s1,s2;
scanf("%d",&n);
for(int i=0;i<n;i++){
pile[i].elem=new int[maxn];
pile[i].elem[0]=i;
pile[i].length++;
}
while(cin>>s1){
if(s1=="quit"){
print_elements();
break;
}
cin>>a>>s2>>b;
find_block(row,col,a);
find_block(row1,col1,b);
if(a!=b&&row!=row1){
//归位
if(s1=="move")goback(a,row,col);
if(s2=="onto")goback(b,row1,col1);
//移动
for(int i=col;i<pile[row].length;i++){
pile[row1].elem[pile[row1].length]=pile[row].elem[i];
pile[row1].length++;
}
pile[row].length=col;
}
}
return 0;
}
3.Boxes in a Line
命令1和命令2将X移出需要知道X的前一个,命令1将X移到Y的左边需要知道Y的前一个,所以不能用单链表,要用双链表,也不能用数组,移动的时间复杂度高。
用数组模拟双链表,用Left和Right两个数组储存每一个数字左右两边的数字是什么。
命令4的反转耗时比较多,所以记录命令4的次数,不执行,根据奇偶判断最后相加的结果(具体看代码解释)。但是命令4的不执行对命令1和2有影响,命令1和命令2依赖于框的绝对位置(遇到命令4不进行反转,在这之后遇到命令1让x在y的左边,为了保持未反转的状态,即让x在y的右边,应该将命令1换成命令2,让x在y的右边)。这里不太理解的可以举例(4和1 2 3)与(2 2 3)两组命令刚好是反转和未反转的关系。
运用余数可以让Right[6]=0,然后再设置数字0的左右两边就可以首尾相连了。
link函数相当于双链表,让两个结点建立联系
命令3交换有一个地方需要注意,如果x和y相邻是特殊情况,要单独进行处理。
#include<iostream>
#include<cstdio>
#include <cstring>
#define ll long long
using namespace std;
int n,Left[100010],Right[100010];
void link(int x,int y)
{
Left[y]=x;
Right[x]=y;
}
int main()
{
int m,kase=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
for(int i=1;i<=n;i++)
{
Left[i]=i-1;
Right[i]=(i+1)%(n+1);运用余数可以让首尾相连
}
Right[0]=1;Left[0]=n;
int op,x,y,flag=0;
while(m--)
{
scanf("%d",&op);
if(op==4) flag=!flag;
else
{
scanf("%d%d",&x,&y);
if(op!=3&&flag) op=3-op;
if(op==3&&Right[y]==x) swap(x,y);//如果x在y的右边,交换x,y,那么x在y的左边了,方便后面统一处理x在y左边的交换。
int lx=Left[x],rx=Right[x],ly=Left[y],ry=Right[y];
if(op==1)
{
if(ly!=x)
{
link(lx,rx);link(ly,x);link(x,y);
}
}else if(op==2)
{
if(ry!=x)
{
link(lx,rx);link(x,ry);link(y,x);
}
}else if(op==3)
{
if(ly==x){
link(lx,y);link(x,ry);link(y,x);
}
else{
link(lx,y);link(x,ry);link(y,rx);link(ly,x);
}
}
}
}
int b=0;//第一个位置左边的数永远是0
ll ans=0;
for(int i=1;i<=n;i++)
{
b=Right[b];//b从头往右被赋值
if(i%2==1) ans+=b;
}
if(flag&&n%2==0) ans=(ll)n*(n+1)/2-ans;//如果命令4的数量为奇数,就需要反转;如果n为奇数,反转与不反转结果是一样的;如果n是偶数,总数-奇数位和就是结果了。
printf("Case %d: %lld\n",++kase,ans);
}
return 0;
}
我之前用的方法没有用数组模型双链表,而是创建了双链表,命令3的交换很方便,但是超时了,因为需要寻找x和y结点,而用数组模拟无需寻找xy结点,能省很多时间。
#include<iostream>
#include <vector>
#include<cstdio>
#include <cstring>
#define ll long long
using namespace std;
int m,n;
typedef struct Lnode{
int data;
struct Lnode *next,*prev;
}Lnode,*LinkList;
void Reverse_L(LinkList &L){
LinkList p,q;
p=L->next;
L->next=NULL;
q=p->next;
while(q){
p->next=L->next;
p->prev=L;
if(L->next!=NULL)L->next->prev=p;
L->next=p;
p=q;
q=q->next;
}
p->next=L->next;
p->prev=L;
L->next->prev=p;
L->next = p;
}
//头插法创建链表
void creatList_H(LinkList &L){
LinkList s;
L=new Lnode;
L->next=L->prev=NULL;
L->prev=NULL;
int t=n;
while(t>0){
s=new Lnode;
s->data=t--;
s->next=L->next;
s->prev=L;
//除第一个节点,后面节点s的插入需要修改s节点的后继的前驱为s,不是L了。
if(L->next!=NULL) L->next->prev=s;
L->next=s;
}
}
ll sum_position(LinkList L,int flag){
LinkList p;
p=L->next;
int i=1;
ll sum=0;
while(p){
if((i++)%2==1)sum+=p->data;
p=p->next;
}
if(flag&&n%2==0){
sum=(ll)n*(n+1)/2-sum;
}
return sum;
}
void find_element(LinkList L,int b,int c,LinkList &p,LinkList &q){
LinkList s;
s=L->next;
while(s){
if(s->data==b){
p=s;
}
if(s->data==c){
q=s;
}
s=s->next;
}
}
int main()
{
int a,b,c,k=1,t;
LinkList L,p,q;
while(scanf("%d%d",&n,&m)!=EOF){
creatList_H(L);
int flag=0;
for(int i=0;i<m;i++){
cin>>a;
if(a==4){
flag=!flag;
}else{
cin>>b>>c;
find_element(L,b,c,p,q);
if(a==3){
p->data=c;
q->data=b;
}
else {
if(flag)a=3-a;
p->prev->next=p->next;
if(p->next!=NULL)p->next->prev=p->prev;
if(a==1){
p->next=q;
p->prev=q->prev;
if(q->prev!=NULL)q->prev->next=p;
q->prev=p;
}
else{
p->prev=q;
p->next=q->next;
if(q->next!=NULL)q->next->prev=p;
q->next=p;
}
}
}
}
printf("Case %d: %lld\n",k++,sum_position(L,flag));
}
return 0;
}