顺序表+单链表

一、顺序表

顺序表中元素的存储是连续的,中间不允许有空。

根据空间分配方式有静态分配和动态分配。

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++;

平均时间复杂度为

O(n)=\frac{1}{n+1}\sum_{i=1}^{n+1}(n-i+1)

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--;

平均时间复杂度为

O(n)=\frac{1}{n+1}\sum_{i=1}^{n}(n-i)

二、单链表 

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.取值和查找

链表的头指针不可以随意改动。

O(n)=\frac{1+2+...+n}{n}=\frac{1}{n}(\frac{n*(1+n)}{2})=\frac{n+1}{2}

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;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值