(新版)SJTU-OJ-1005. Linked List

题目描述

众所周知,第 0 次机考总有一道题是单链表,这次也不例外。

你需要实现一个单链表 L L L,一些需要实现的操作如下:

  1. 利用数组初始化该单链表,该操作只会在初始时被调用 1 次;

  2. 在单链表第 i i i 个元素后插入元素 x x x。特别地,如果 i = 0 i = 0 i=0,则表示在表头插入元素;如果 i = ∣ L ∣ i = |L| i=L,则表示在表尾插入元素;如果出现除 0 ≤ i ≤ ∣ L ∣ 0 \leq i \leq |L| 0iL之外的其他情况,无视当前操作

    注意:元素从 1 开始编号

  3. 删除单链表的第 i i i 个元素。特别地,如果出现除 1 ≤ i ≤ ∣ L ∣ 1 \leq i \leq |L| 1iL之外的其他情况,无视当前操作

  4. 将单链表奇偶交换,也就是说原来编号为 2 k 2k 2k的元素与原来编号为 2 k − 1 2k - 1 2k1的元素交换。如果单链表长度为奇数,则剩下的最后一个元素不需要进行交换;你需要在原地进行该操作,也就是说,这个操作只能使用 O ( 1 ) O(1) O(1)的额外空间;

  5. 询问单链表的第 i i i个元素。特别地,保证不会出现除 1 ≤ i ≤ ∣ L ∣ 1 \leq i \leq |L| 1iL之外的其他情况。

  6. 询问整个单链表,即按编号顺序输出单链表中所有元素。

  7. 在程序的最后,你需要释放所有动态申请的内存。

本题提供代码模板,也欢迎直接从头开始写代码的选手。

输入格式

程序开始时,你需要读入一个数 n n n 和一个有 n n n 个数的数组;然后调用操作 0 进行初始化。接下来你会读入一个数 m m m 表示操作个数。接下来 m m m行,每行第一个数为 o p op op,表示操作类型。

  • 如果 o p = 1 op = 1 op=1,表示当前操作为操作 1,接下来读入两个数 i,xi,x,含义如上文所示;
  • 如果 o p = 2 op = 2 op=2,表示当前操作为操作 2,接下来读入一个数 ii,含义如上文所示;
  • 如果 o p = 3 op = 3 op=3,表示当前操作为操作 3;
  • 如果 o p = 4 op = 4 op=4,表示当前操作为操作 4,接下来读入一个数 i i i,含义如上文所示;
  • 如果 o p = 5 op = 5 op=5,表示当前操作为操作 5。

在所有操作后,将会调用操作 6 来释放动态分配的内存。

输出格式

对于每一个操作 4 或操作 5,在屏幕上输出操作的答案。注意,每次操作 4 或操作 5 完成后,务必在输出后附带换行,以区分每个输出。具体参见样例输入输出。

样例输入

5
2 3 3 3 4
10
3
5
1 2 6
1 5 4
1 10 3
2 7
4 4
4 3
3
5

样例输出

3 2 3 3 4
3
6
2 3 3 6 4 3

数据范围

1 ≤ n , m ≤ 3000 1 \leq n, m \leq 3000 1n,m3000,链表中的所有数在 int 范围内。

请务必使用单链表来完成本题!!助教将会人工判断你的代码是否使用单链表,若否则本题计 0 分。

你可以使用如下的代码模板,程序的大部分已帮你写好,你只需要填写 TODO 中的内容即可;当然,你也可以自己从头开始写代码。

# include <iostream>
# include <stdio.h>
using namespace std;

struct LinkList {
  // TODO: Define some variables of struct LinkList here.
  void Initialize(int *a, int n) {
    // TODO: use a[0 ... n-1] to initialize the link list.
  }
  void Insert(int i, int x) {
    // TODO: insert element x after i-th element.
  }
  void Delete(int i) {
    // TODO: delete element i
  }
  void EvenOddSwap() {
    // TODO: Swap the even-indexed element with the corresponding odd-indexed element.
  }
  void Query(int i) {
    // TODO: Print the value of the i-th element on the screen.
  }
  void QueryAll() {
    // TODO: Print the link list on the screen.
  }
  void ClearMemory() {
    // TODO: Clear the memory.
  }
};

LinkList L;

int main() {
    ios :: sync_with_stdio(false);
  cin.tie(0); cout.tie(0);

  int n, *a;
  cin >> n;
  a = new int [n];
  for (int i = 0; i < n; ++ i) cin >> a[i];
  L.Initialize(a, n);
  delete [] a;

  int m, op, i, x;
  cin >> m;
  while(m --) {
    cin >> op;
    switch(op) {
      case 1: 
        cin >> i >> x;
        L.Insert(i, x);
          break;
      case 2:
        cin >> i;
        L.Delete(i);
        break;
      case 3:
        L.EvenOddSwap();
           break;
      case 4:
        cin >> i;
        L.Query(i);
        break;
      case 5:
        L.QueryAll();
        break;
    }
  }

  L.ClearMemory();
  return 0;
}

题目分析

       首先,作为一个机考题,如果是没有学习过的应该是蛮难了,但是似乎ACM班的大佬应该很多,这题肯定是小意思。~~(而且我0基础看了一下午也就学会了?这说明不难)~~不过要是有单链表以及指针的基础话并不困难。而且客观的说,本题考察的也很基础,就是普通的单链表操作,包括增加、删除、交换、修改。似乎看出来这个题目的目的,就是督促学生预习23333。

       另外我觉得emmm……写代码的时候最重要的一点就是及时输出检测,在我第一次写这道题的时候,一口气写完了所有的功能。

       没错,然后失败了,又重头写的2333。

       所以写代码一定记得及时输出,写一个功能检测一个功能,不然,,,,嘿嘿,,,头发,你懂的。。。。

单链表

       单链表嘛,从个人的感觉就是emmm在黑夜里面,操作一个黑箱子,看不见摸不着,全靠指针来指示方向。单链表的每一个节点包括两个部分,数据部分和指针部分。如下表所例。

数据部分指针部分
int dataint * p

       单链表是一个链子,前一个节点指向后一个节点,但是在内存中的位置我们不知道。这就是链表和数组的最大区别。数组在内存里面是连续的内存空间,但是链表不是。操作链表的时候一定要小小,应为一旦失误,谁都不知道这个指针会指向哪里,下一个结点会在哪里,下下个节点会在哪里也没有人知道。一旦断了或者地址丢失了,整个链表相当于作废了。

       通常比较好的方法是定义单链表的时候开头定义一个指针head头指针,然后尾部定义一个NULL空指针,然后遍历的时候就比较方便啦!有始有终,便于查找。

题目解答

一、定义结构体

​ 我们开始正经的看题目。首先定义一个结构体。

struct LinkList 
{
    int data;			// 定义一个结构体,前一个部分存放数据
    LinkList *next;		// 定义一个结构体,后一个部分存放指针,指向下一个节点。
};

       然后要定义的是一些全局变量,考虑到整个程序中要统计数组的个数,要有一个头指针和尾部指针,还有一个指针要跑腿,在创建和查找的时候遍历这个链表,我们定义为*p

LinkList *head,*rear,*p;
int n;
二、链表初始化

       第一个要实现的功能是初始化单链表,题目是通过一个函数initialize实现的,而函数之前有一个数组a[n],初始化单链表有下面几个步骤

  1. 创建第一个节点,head指向表头,rear指向表尾,第一个节点是也是最后一个节点。

  2. for循环读取数据,读入数据后要申请空间,空间里面的数据部分要存放新来的数据,前一个节点的指针部分要指向这个新的节点;

  3. 读取结束要把尾指针更新为NULL。

       步骤一:创建空链表

head = rear = new LinkList;			 // 申请空间,头指针,尾指针都定义过来。(其实尾指针可以最后再说)

       步骤二、三:空链表初始化

void Initialize(int a[], int n)
{
    for (int i = 0; i < n; i++)
    {
        p = new LinkList;			// 申请空间
        p->data = a[i];				// data部分放数据
        rear->next = p;				// 之前的尾指针指示的是上一个节点,rear->next 表示上一个节点的指针域
        rear = p;					// 更新尾指针
    }
    rear->next = NULL;				// for循环结束,尾指针也要更新为空指针 NULL
}
三、链表的输出

       链表的输出由void QueryAll()负责,输出就由一个for遍历一次即可。

void QueryAll()
{
    p = head->next;
    while (p != NULL)
    {
        if (p->next == NULL)
            cout << p->data;				// 输出结果
        else
            cout << p->data << " ";			// 输出结果
        p = p->next;					   // 考虑到输出的之间要空白 if实现
    }
    cout << endl;
}
四、链表的访问

       链表的访问由void Query(int i)负责,不妨我们修改一下,把这个函数功能改为把指针p定位到第i个元素。输出放在主函数里面。

void Query(int i) //这个函数用于把指针p定位到第i个元素
{
    p = head->next;
    for (int k = 0; k < i - 1; k++)
    {
        p = p->next;
    }
    // cout << p->data << endl;     测试有效!
}
五、链表的元素删除

       链表的删除需要以下几个步骤:

  1. 定义一个临时指针,保存要删除节点前面的位置的地址。

  2. 更改要删除节点前面的节点的指针部分,使其指向后面节点的后面的节点。

  3. 通过指针回收要删除的节点,delete!

    考虑到这个题目有点特殊,可能要删第一个,也可能要删除最后一个,也可能传入的数非法,那就不操作,所以if判断。

    另外,后两个if可以合并。

void Delete(int i)						// 删除第 i 个节点
{
    LinkList *temp;
    if (i == 1)							// 删除首节点
    {
        LinkList *temp;					// 定义一个临时指针
        temp = head->next;
        head->next = temp->next;		// 更改要删除节点前面的节点的指针部分,使其指向后面节点的后面的节点。
        delete temp;					// 回收空间要记得哦
        n--;							// 更新链表元素的总数
    }
    if (i == n)							// 删除尾部节点
    {
        LinkList *temp;					// 定义一个临时指针
        Query(n - 1);					// 利用之前已经写好的函数,把temp指针定位到要删除指针的前面。
        temp = p->next;					// 临时指针保存要删除节点前面的位置的地址
        p->next = temp->next;			// 更改要删除节点前面的节点的指针部分,使其指向后面节点的后面的节点。	
        delete temp;					// 回收空间要记得哦
        n--;							// 更新链表元素的总数
    }
    if (i > 1 && i < abs(n))			// 删除中间节点
    {
        Query(i - 1);				    // 利用之前已经写好的函数,把temp指针定位到要删除指针的前面。
        LinkList *temp;
        temp = p->next;					// 临时指针保存要删除节点前面的位置的地址
        p->next = temp->next;			// 更改要删除节点前面的节点的指针部分,使其指向后面节点的后面的节点。	
        delete temp;					// 回收空间要记得哦
        n--;							// 更新链表元素的总数
    }

    else
        return;
}
六、链表的元素增加

       要增加链表的元素,分三种情况:链表首元素前添加,链表中间元素添加,链表末元素前添加。

       链表的添加步骤如下:

  1. 申请新空间,在data区域存放要添加的元素值

  2. 如果在第 i i i 个节点后面添加节点,可以调用我们之前写过的函数Query(i);把p指针指的第 i i i 个节点

  3. 注意顺序!先修改新空间的指针域的数据,使其指向第 i i i 个节点后面的节点

  4. 注意顺序!再修改第 i i i 个节点的指针域,使其指向新空间,注意,就这样就可以了。

  5. 结束!

    不要画蛇添足回收空间

void Insert(int i, int x) 
{
        // TODO: insert element x after i-th element.
        LinkList *temp;
        if (i == 0)
        {
            temp = new LinkList;
            temp->data = x;
            temp->next = head->next;
            head->next = temp;
            n++;
        }
    
        if (i > 0 && i <= abs(n))
        {
            Query(i);					//申请新空间
            temp = new LinkList;		//在data区域存放要添加的元素值
            temp->data = x;				//先修改新空间的指针域的数据,使其指向第i个节点后面的节点
            temp->next = p->next;
            p->next = temp;				//再修改第 $i$ 个节点的指针域,使其指向新空间,注意,就这样就可以了。
            n++;						// 更新链表元素的总数n(n是全局变量)
        }
        else
            return;						// 非法数据不予处理
}
七、链表奇偶元素互换

       老套路,把p指针先指向链表头部吧。

       偷偷的开一个空间,临时存放数据,然后就交换数据,就行我们学过的交换数组的数据。

       p指针最后往后移动两个位置,例如从第head->2,2->4……之后再开始循环

       for循环中表示次数,考虑到n/2的特点,可以满足要求。

void EvenOddSwap() 
{
    // TODO: Swap the even-indexed element with the corresponding odd-indexed element.  
    p = head;
    for (int j = 1; j <= n / 2; j++)
    {
        int tempnum = p->next->data;
        p->next->data = p->next->next->data;
        p->next->next->data = tempnum;
        p = p->next->next;
    }
}

       其实最开始我是这么写的,先保存奇数节点的data数值,再调用delete删除节点,再把保存的数组添加到正确位置

       但是明显很复杂,Query(k)的调用与j的遍历明显会超时,所以不建议使用。

void EvenOddSwap() 
{
    // TODO: Swap the even-indexed element with the corresponding odd-indexed element.
    p = head;
    LinkList *temp1,*temp2;
    for (int j = 1; j <= n / 2; j++)
    {
        int k = 2 * j - 1;
        Query(k);
        int tempnum = p->data;
        Delete(k);
        Insert(k, tempnum);
    }
}
八、完整代码
#include<iostream>
#include<stdio.h>
using namespace std;

struct LinkList 
{
    int data;
    LinkList *next;
};

LinkList *head,*rear,*p;
int n;

void Initialize(int a[], int n)
{
    for (int i = 0; i < n; i++)
    {
        p = new LinkList;
        p->data = a[i];
        rear->next = p;
        rear = p;
    }
    rear->next = NULL;
}

void QueryAll()
{
    p = head->next;
    while (p != NULL)
    {
        if (p->next == NULL)
            cout << p->data;
        else
            cout << p->data << " ";
        p = p->next;
    }
    cout << endl;
}

void Query(int i) //这个函数用于把指针p定位到第i个元素
{
    p = head->next;
    for (int k = 0; k < i - 1; k++)
    {
        p = p->next;
    }
    // cout << p->data << endl;     测试有效!
}

void Delete(int i)
{
    LinkList *temp;
    if (i == 1)
    {
        temp = new LinkList;
        temp = head->next;
        head->next = temp->next;
        delete temp;
        n--;
    }
    if (i == n)
    {
        LinkList *temp;
        Query(n - 1);
        temp = p->next;
        p->next = temp->next;
        delete temp;
        n--;
    }
    if (i > 1 && i < abs(n))
    {
        Query(i - 1);
        LinkList *temp;
        temp = p->next;
        p->next = temp->next;
        delete temp;
        n--;
    }

    else
        return;
}

void Insert(int i, int x) 
{
        // TODO: insert element x after i-th element.
        LinkList *temp;
        if (i == 0)
        {
            temp = new LinkList;
            temp->data = x;
            temp->next = head->next;
            head->next = temp;
            n++;
        }
        // if (i == abs(n))
        // {
        //     Query(n);
        //     temp = new LinkList;
        //     temp->data = x;
        //     temp->next = p->next;
        //     p->next = temp;
        //     n++;
        // }
        if (i > 0 && i <= abs(n))
        {
            Query(i);
            temp = new LinkList;
            temp->data = x;
            temp->next = p->next;
            p->next = temp;
            n++;
        }
        else
            return;
}


void EvenOddSwap() 
{
    // TODO: Swap the even-indexed element with the corresponding odd-indexed element.
    
    p = head;
    LinkList *temp1,*temp2;
    // for (int j = 1; j <= n / 2; j++)
    // {
    //     // int k = 2 * j - 1;
    //     // Query(k);
    //     // int tempnum = p->data;
    //     // Delete(k);
    //     // Insert(k, tempnum);

    // }

    for (int j = 1; j <= n / 2; j++)
    {
        int tempnum = p->next->data;
        p->next->data = p->next->next->data;
        p->next->next->data = tempnum;
        p = p->next->next;
        // temp1 = new LinkList;
        // temp2 = new LinkList;
        // temp1->next = temp2;
        // temp1->data = p->next->data;
        // temp2->data = p->next->next->data;
        // temp2->next = p->next->next->next;
        // LinkList *dp;
        // dp = p->next->next;
        // delete dp;
        // dp = p->next;
        // delete dp;
        // p->next = temp1;
        // p = temp2->next;
    }
}


void ClearMemory() 
{
    // TODO: Clear the memory.
    LinkList *q;
    p = head;
    while (p != NULL)
    {
        q = p->next;
        delete p;
        p = q;
    }
}


int main()
{
    head = rear = new LinkList;

    cin >> n;
    int a[n];
    for (int i = 0; i < n; ++ i) 
    {
        cin >> a[i];
    }
    int m, op, i, x;
    cin >> m;
    Initialize(a, n);
    while(m--)
    {
        cin >> op;
        switch (op)
        {
        case 1:
            cin >> i >> x;
            Insert(i, x);
            break;
        
        case 2:
            cin >> i;
            Delete(i);
            break;
        case 3:
            EvenOddSwap();
            break;
        case 4:
            cin >> i;
            Query(i);
            cout << p->data << endl;
            break;
        case 5:
            QueryAll();
            break;

        default:
            break;
        }

        // cout << "当前有" << n << endl;
    }
    
    ClearMemory();
    system("pause");
    return 0;
}

       补充更新,考虑到我之前的做法没有用题目的模板,而且把函数都拆分了出来(当时确实不是很会操作,没有学习过类和结构体的知识)现在学了一丢丢再来补充更新一下,按照模板的写法。
       之前的我也尝试过按照模板,但是在结构体里面我要定义单链表的每一个节点,那就是要一个int 整型变量和一个 next 指针变量,另外还要三个变量,分别是*head,*rear,*p分别指示头结点,尾结点,还有用来操作的指针,定义的时候有点乱,解决方法是,在结构体里面定义一个结构体(事实证明可行)就像下面的代码一样,里面的结构体 LinkList 是用来描述节点的,每个节点有 datanext 组成,外面的结构体是一个包裹的,就相当于一个类,里面的变量是私有的 private (为啥?因为外部的 main 函数不需要调用到里面的变量,所以用私有的)里面的函数是 public(为啥?因为外部的 main 函数要调用这个里面的函数,所以是公有的。(关于私有共有还是看课本那个有理数计算的案例,并不是所有函数都是公有,课本里面那个有理数约分的那个函数就是私有的,因为这个函数外面不需要调用,所以设置为私有 private
       写到这里我就有点生气了,那这个出题人就有点没搞好,直接定义一个类嘛,然后在类里面方函数不香嘛,偏偏要搞一个结构体,结构体里面再放一个结构体,都让人要混乱了
       按照模板的代码如下:(通过结构体访问成员函数)

#include<iostream>
#include<stdio.h>
using namespace std;

int n,*a;			// 由于n是链表里面元素的个数,所以我把它移到全局变量
int temp_query;		// 由于我的Query()函数只能把指针定位到制定元素,不能输出,所以定义这个变量

struct test
{
    struct LinkList 
    {
        int data;
        LinkList *next;
    };								// 这个结构体是单链表的结构体

    LinkList *head,*rear,*p;		// 定义三个指针变量
   
    void Initialize(int a[], int n)
    {
        head = rear = new LinkList;
        for (int i = 0; i < n; i++)
        {
            p = new LinkList;
            p->data = a[i];
            rear->next = p;
            rear = p;
        }
        rear->next = NULL;
    }

    void QueryAll()
    {
        p = head->next;
        while (p != NULL)
        {
            if (p->next == NULL)
                cout << p->data;
            else
                cout << p->data << " ";
            p = p->next;
        }
        cout << endl;
    }

    void Query(int i) //这个函数用于把指针p定位到第i个元素
    {
        p = head->next;
        for (int k = 0; k < i - 1; k++)
        {
            p = p->next;
        }
        temp_query = p->data;
    }

    void Delete(int i)
    {
        LinkList *temp;
        if (i == 1)
        {
            temp = new LinkList;
            temp = head->next;
            head->next = temp->next;
            delete temp;
            n--;
        }
        if (i == n)
        {
            LinkList *temp;
            Query(n - 1);
            temp = p->next;
            p->next = temp->next;
            delete temp;
            n--;
        }
        if (i > 1 && i < abs(n))
        {
            Query(i - 1);
            LinkList *temp;
            temp = p->next;
            p->next = temp->next;
            delete temp;
            n--;
        }
        else
            return;
    }

    void Insert(int i, int x) 
    {
            // TODO: insert element x after i-th element.
            LinkList *temp;
            if (i == 0)
            {
                temp = new LinkList;
                temp->data = x;
                temp->next = head->next;
                head->next = temp;
                n++;
            }
            if (i > 0 && i <= abs(n))
            {
                Query(i);
                temp = new LinkList;
                temp->data = x;
                temp->next = p->next;
                p->next = temp;
                n++;
            }
            else
                return;
    }


    void EvenOddSwap() 
    {
        // TODO: Swap the even-indexed element with the corresponding odd-indexed element.
        p = head;
        LinkList *temp1,*temp2;
        
        for (int j = 1; j <= n / 2; j++)
        {
            int tempnum = p->next->data;
            p->next->data = p->next->next->data;
            p->next->next->data = tempnum;
            p = p->next->next;
        }
    }


    void ClearMemory() 
    {
        // TODO: Clear the memory.
        LinkList *q;
        p = head;
        while (p != NULL)
        {
            q = p->next;
            delete p;
            p = q;
        }
    }
};

struct test L;

int main()
{
    

    cin >> n;
    a = new int [n];
    for (int i = 0; i < n; ++ i) 
    {
        cin >> a[i];
    }
    L.Initialize(a, n);
    delete [] a;

    int m, op, i, x;
    cin >> m;
    while(m--)
    {
        cin >> op;
        switch (op)
        {
        case 1:
            cin >> i >> x;
            L.Insert(i, x);
            break;
        case 2:
            cin >> i;
            L.Delete(i);
            break;
        case 3:
            L.EvenOddSwap();
            break;
        case 4:
            cin >> i;
            L.Query(i);
            cout << temp_query << endl;
            break;
        case 5:
            L.QueryAll();
            break;

        default:
            break;
        }
    }
    L.ClearMemory();
    system("pause");
    return 0;
}

       结构体默认是 public 所以按照题目的这个模板就有点不合适,不过这题共有私有不必纠结,直接做就可以。(2021-7-26更新)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值