DS01-线性表

0.PTA得分截图

1.本周学习总结

1.1 总结线性表内容❤️

  • 顺序表💕
    • 定义:将所有元素按照顺序存储方法进行存储的结构,类似数组。
      创建一个结构体,其中包含数据域和顺序表长度,例如:
typedef struct{
    ElemType data[10];  //存放顺序表中的元素
    int length;              //顺序表的长度
}SqList;
  • 特点:1.地址相邻
    2.可以实现随机抽取某一位置的元素
  • 顺序表的插入💕
  • 其伪代码为
if(i<1||i>L->length+1) eixt(1)
end if//要插入的位置不存在 停止运行
i--;//让i从0开始记数
for(j=L->length;j>i;j--)//从结尾开始后移
    L->data[j]=L->data[j-1];
    end for
L->data[i]=e;//插入
L->length++;//表长增加
  • 顺序表的删除💕
  • 其伪代码为
if(i<1||i>L->length+1) eixt(1)
end if//要插入的位置不存在 停止运行
i--;//让i从0开始记数
e=L->data[i];//删除的数为第i个位置
for(int j=i;j<L->length-1;j++)
    L->data[j]=L->[j+1];
L->length--;//顺序表长度-1

容易出错的地方是:当要删除多个元素的时候,遇到全删除的情况
使用三重循环,记得要i--从头开始遍历,不然无法全删除

  • 链表💕
    • 定义,创建一个结构体,其包括数据域和指针域;其本质上就是结构体变量和结构体变量连接在一起
typedef struct LNode  		//定义单链表结点类型
{
	ElemType data;
	struct LNode *next;		//指向后继结点
} LNode, *LinkList;
  • 动态创建一个链表:动态内存申请+模块化设计
  • 结构体指针申请内存后变成结构体变量 变量使用前需要初始化
  • 头插法💕

    -用代码表示就是
newNode->next=headNode->next;//先接上后继关系
headNode->next=newNode;
  • 头插法具体代码如下
LinkList headNode,newNode;
headNode=new LNode;
headNode->next=NULL;
for(i=0;i<n;i++)//n为插入的节点个数
{
    newNode=new LNode;
    cin >> newNode->data;
    newNode->next=headNode->next;
    headnext->next=newNode;
}
  • 尾插法💕


  • 具体代码如下
        L = new LNode;
	L->next = NULL;
	LinkList tailNode, ptrNode;
	tailNode = L;
	for (int i = 0; i < n; i++)
	{
		ptrNode = new LNode;
		cin >> ptrNode->data;
		//ptrNode->next = tailNode;此步骤可有可无
		tailNode->next = ptrNode;
		tailNode = ptrNode;
	}
	tailNode->next = NULL;
  • 链表的插入💕


  • 链表的删除💕

  • 有序表💕

    • 有序表是线性表的一种,其一般是以递增递减的顺序储存的
    • 有序链表的插入💕


      此处遇到问题,如何寻找插入结点位置?
      bigo!利用前驱的关系来寻找插入位置



    • 例如在有序链表插入一个数e 具体代码实现如下
LinkList newNode = new LNode;
	LinkList preNode;
	preNode = L;
	while (preNode->next&&preNode->next->data < e)
	{
		preNode = preNode->next;
	}
	newNode->data = e;
	newNode->next = preNode->next;
	preNode->next = newNode;
  • 有序单链表的删除💕




  • 具体代码实现如下
LinkList posNode, posNodeFront;
	posNode = L->next;
	posNodeFront = L;
	while (posNode != NULL && posNode->data != e)
	{
		posNodeFront = posNode;
		posNode = posNode->next;//下移
		if (posNode == NULL) printf("%d找不到!", e);
	}
	if (posNode) {
		posNodeFront->next = posNode->next;
		delete posNode;
	}
  • 有序链表的合并💕

  • 具体代码实现如下

void MergeList(LinkList &L1, LinkList L2)
{
	LinkList head,p;
	head = L1;
	while (head->next&&L2->next)
	{
		if (head->next->data > L2->next->data)
		{
			p = new LNode;
			p->data = L2->next->data;
			p->next = head->next;
			head->next = p;
			L2 = L2->next;
		}
		else if (head->next->data == L2->next->data)
		{
			L2 = L2->next;
		}
		head = head->next;
	}
	if (head->next == NULL) head->next = L2->next;
}
  • 双链表💕
    • 双链表每个节点有2个指针域,一个指向后继结点,一个指向前驱结点。
typedef struct DNode   
 {	ElemType data;
        struct DNode *prior;    //指向前驱结点
	struct DNode *next;     //指向后继结点
  } DLinkList;
  • 特点:
    1.不管是哪个位置的结点都可以找到他的前驱和后继关系
    2.不管从哪里开始都可以找到别的位置的结点
  • 循环链表💕
    • 特点:将表中尾结点的指针域改为指向表头结点,整个链表形成一个环。由此从表中任一结点出发均可找到链表中其他结点。
      从循环链表中的任何一个结点的位置都可以找到其他所有结点
      但是没有尾端

1.2.谈谈你对线性表的认识及学习体会。❤️

链表作为C语言中最难的部分,是非常抽象的,所以我们一定要用画图来帮助我们理解,我就是因为在家里懒得找笔画链表导致我理解链表混乱,及我们要注意链表的后继和前驱关系才能运用好链表,对于时间复杂度和空间复杂度是我一直都不能理解的,所以老师提问的时候我都很慌,因为我不会,双链表和循环链表的插入和删除比单链表更加的复杂,没有遇到专门的题也不能理解,总之还是画图把,画图的重要性不管是在自己写链表还是阅读别人的链表的时候,都应该画图。

2.PTA实验作业

2.1 编程题: 7-1 两个有序序列的中位数❤️

已知有两个等长的非降序序列S1, S2, 设计函数求S1与S2并集的中位数。有序序列A0,A1,⋯,AN−1A_0, A_1, \cdots, A_{N-1}A0,A1,⋯,AN−1的中位数指A(N−1)/2A_{(N-1)/2}A(N−1)/2的值,即第⌊(N+1)/2⌋\lfloor(N+1)/2\rfloor⌊(N+1)/2⌋个数(A0A_0A0为第1个数)。

输入格式:

输入分三行。第一行给出序列的公共长度N(0<<<N≤\le≤100000),随后每行输入一个序列的信息,即N个非降序排列的整数。数字用空格间隔。

输出格式:

在一行中输出两个输入序列的并集序列的中位数。


2.1.1 伪代码及思路

  • 已知:L1和L2并集即重复数据也要放进一条链中
    两条链长度n相等即当合并到一条链中第n个数即是中间的那位数
    • 思路:💕

    • 伪代码 💕
LinkList headNode,newNode;
int i=0,num=0;
headNode=L1;
while (headNode->next != NULL && L2->next != NULL)
      if (headNode->next->data >= L2->next->data)
      赋L2的值给newNode
      把newNode插入headNode->next的前面,L2下移
      end if
      headNode下移
      每一轮都要i++记录已经归并的个数
      if(i==n) return headNode->data;//归并的第n个数就是中位数
      end if
end while    

2.1.2 代码截图



2.1.3 本题PTA提交列表说明


这答题我表示由于不太理解题目的关系,我用了两种方法都错了
Q1部分正确:我归并的时候将重复数据除去然后按照数学数中位数的方式写
Q2部分正确:第一种方法行不通之后我使用了快慢指针,但快慢指针过不了最后一个测试点,一开始我以为是因为遍历太多次了,但也不是运行超时的问题,卡住了。
A3答案正确:后来我查看了题目,题目应该是不需要除去重复数据,所以不需要算中位数,因为两条链长度相等,归并后第n个数则就是中位数。

2.2 函数题:6-8 jmu-ds-链表倒数第m个数❤️

已知一个带有表头节点的单链表,查找链表中倒数第m个位置上的节点。

  • 输入要求:先输入链表结点个数,再输入链表数据,再输入m表示倒数第m个位置。
  • 输出要求,若能找到则输出相应位置,要是输入无效位置,则输出-1

函数接口定义

int Find(LinkList L, int m );
  • L:单链表
  • m:倒数第m个位置

2.2.1 伪代码及思路

  • 思路:💕
  • 伪代码 💕
p=L->next;
q=L->next;
posNode=L->next;
遍历链表posNode得到n个节点
if(m<=0||m>n) return -1;
end if
for i=0 to m-1
    p=p->next
end for
while(p->next)
    p=p->next;
    q=q->next;
end while
return q->next->data;

2.2.2 代码截图

2.2.3 本题PTA提交列表说明


Q1部分正确:返回的应该是p->next->data 而不是 p->data
Q2部分正确:没有判断位置无效条件
因为p->next->data才是倒数第m个数

2.3 函数题:6-11 链表分割❤️

该函数实现链表的分割。尾插法建好初始链表L={a1,b1,a2,b2,.....an,bn}。分割2个链表,其中L1和L共享头结点,分割后链表如下:

  • L1:

  • L2:

  • L为原链表,L1和L共享头结点,正序链表,L2为倒序链表

    输入样例:

    5
    1 2 3 4 5   
    

    输出样例:

    1 3 5
    4 2
    

2.3.1 伪代码及思路

  • 思路:💕

  • 伪代码:💕

LinkList head,ptr;
	head = L->next;
	L2 = new LNode;
	L2->next = NULL;
	while (head&&head->next != NULL)
	ptr等于head->next
	head->next=head->next->next;第一个节点连接下下一个节点
	让head下移
	把ptr以头插法的方式插到L2
	end while
	L1=L;

2.3.2 代码截图

2.3.3 PTA提交列表及其说明


Q1答案错误:此处有一个head下移 原来写的是head=head->next->next 下移两位
A1:只需要下移一位 因为head前面跨越了一个节点 所以自然被跨越的节点就不见了所以不需要在跨越一遍了

3.阅读代码

3.1 题目及解题代码❤️

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.
  • 解题代码💕
class Solution {
public: 
   ListNode* swapPairs(ListNode* L) {
        if(!L || !L->next)   return L;
        ListNode* head = new ListNode(-1),*pre = head; 
        head->next = L;
        while(pre->next && pre->next->next){
            ListNode*temp = pre->next->next;
            pre->next->next = temp->next;
            temp->next = pre->next;
            pre->next = temp;
            pre = temp->next;
        }
        return head->next;
    }
};

3.1.1 该题设计思路

  • 思路:💕

3.1.2 该题伪代码

  • 伪代码:💕
if 链表为空 返回头节点
end if
申请head空间为-1 让第一个前驱等于head
head->next=L
while (pre-> && pre->next->next)//交换到不为空时
        temp=pre->next->next;
        开始交换两两的关系
        这里不详写了
end while
return head->next;//返回已经完成交换好的链
  • 时间复杂度和空间复杂度
    • 时间复杂度:因为只遍历了一次链表 链表长度为n 所以时间复杂度为O(n)
    • 空间复杂度:只是几个常数的运用 所以空间复杂度为O(1)

3.1.3 运行结果

3.1.4 分析该题目解题优势及难点

  • 优势:
    这题主要是通过前驱和中间的temp来进行两两交换的,只需要断开连接……遍历一次链表就可以完成,效率很高
  • 难点:
    在于第一次交换的时候,头节点会变,头的指向也会变所以要用一个head来保存它的头 最后通过它进行返回整条链,因为前驱pre是会变化的
    在交换的时候要小心断链的情况,否则它可能会无法前进,断链的时候要考虑这一步断链会造成什么后果,进行合理的断链

3.2 题目及解题代码❤️

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

  • 解题代码💕
class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if(head==NULL) return NULL;
        if(k==0) return head;
        ListNode * start=new ListNode(0);
        start->next=head;
        ListNode* tmpHead=start;
        ListNode* tmpRear=head;
        int len=0,tmpk=k;
        while(tmpHead->next!=NULL &&tmpk>0)//这一步是为了判断len和k的关系
        {
            tmpk--;
            len++;
            tmpHead=tmpHead->next;
        }
        if(k%len==0 && tmpHead->next==NULL ) return head;//①
        if(tmpk==0)//③
        {
            tmpHead=tmpHead->next;
            while(tmpHead->next!=NULL)
            {
                tmpHead=tmpHead->next;
                tmpRear=tmpRear->next;
            }
        }
        else if(len<k)//②
        {
            tmpk=k%len;
            tmpHead=head;
            while(tmpk-->0)
            {
                tmpHead=tmpHead->next;
            }
            while(tmpHead->next!=NULL)
            {
                tmpHead=tmpHead->next;
                tmpRear=tmpRear->next;
            }
        }
        tmpHead->next=start->next;
        start->next=tmpRear->next;
        tmpRear->next=NULL;
        return start->next;
    }
};

3.2.1 该题设计思路

  • 思路:💕
    1)巧妙之处在于利用tmpHead和tmpRear来取需要旋转的链然后这一部分的链接到头结点前面
    2)其次就是利用tmpk,tmpk不仅可以判断len和k的关系还可以控制当k>len的时tmpHead需要下移多少位,从而控制需要旋转的链的尾巴
    3)额外头节点start用来方便更换头节点的位置

3.2.2 该题伪代码

  • 伪代码:💕
ListNode* rotateRight(ListNode* head, int k)
  if 链为空 返回链
  if k为0 返回链
  ListNode *start=new ListNode(0);start->next=head;
  ListNode *tmpHead=start,*tmpRear=head;
  int len=0,tmpk=k;
  判断len和k的关系
  if(k%len==0&&tmpHead->next==NULL) 返回链
  if(tmpk==0)这是k<len的情况
  让tmpHead先走一步然后和tmpRear一起走直到tmpHead->next=NULL
  if(len<K)
  找新的k;tmpk=k%len;
  让tmpHead先走tmpk步然后和tmpRear一起走直到tmpHead->next==NULL
  把tmpHead的尾连到start->next;
  再把start的尾连到tmpRear->next;
  tmpRear的为置空;
  返回 start->next;
  • 时间复杂度和空间复杂度
    • 时间复杂度:都是遍历链表所以时间复杂度为O(n)
    • 空间复杂度:按照我的观察。。应该是O(1),没有二维数组也没有数组

3.2.3 运行结果

3.2.4 分析该题目解题优势及难点

  • 优势:
    它不需要一个一个移动,而是直接取一部分的链直接放到头的部分,非常妙!
  • 难点:
    要实现这种操作必须搞清楚k和len的关系,以及tmpk的运用否则很容易出错
    其次要考虑tmpRear最后的next没了一定要置空,不然会编译错误的!
posted @ 2020-03-08 16:08  雪梨wink  阅读(278)  评论(0编辑  收藏  举报