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没了一定要置空,不然会编译错误的!