0.PTA得分截图
1.本周学习总结
1.1 总结线性表内容
1.线性表定义
线性表是具有相同特性的数据元素的一个有序序列。
2.顺序表
(1)结构体定义
#define MAXSIZE 50//最大长度
typedef int ElemType;
typedef struct
{
ElemType *elem;//指向数据元素的基地址
int length; //线性表当前长度
} SqList;
或者
#define MAXSIZE 50//最大长度
typedef int ElemType;
typedef struct
{
ElemType data[MaxSize];//存放顺序表中的元素
int length; //顺序表的长度
} SqList;
建议:typedef SqList *List;
(2)顺序表的基本操作
1.初始化顺序表
typedef int ElemType;
typedef struct
{
ElemType data[MaxSize];//存放顺序表中的元素
int length; //顺序表的长度
} List;
typedef List *SqList;
void CreateList(SqList &L,int n)
{
int i;
L=new List;
L->length=n;
for(i=0;i>L->data[i];
}
}
2.销毁线性表DestroyList(L)
结果是释放线性表L占用的内存空间。
void DestroyList(SqList *&L)
{
free(L);
}
3.输出线性表DispList(L)
void DispList(List L)
{ int i;
if (ListEmpty(L)) return;
for (i=0;ilength;i++)
cout<data[i]<<" ";
}
4.获取L中第i个元素
返回L中第i个元素的值,存放在e中。1≤i≤ListLength(L)
bool GetElem(List L,int i,ElemType &e){
if (i<1 || i>L->length)
return false;
e=L->data[i-1];
return true;
}
5.按元素值查找
int LocateElem(List L, ElemType e)
{
for(int i=0; ilength;i++)
if(L->data[i]==e)
return i+1; //返回元素的逻辑位序
return 0;
}
6.插入元素
插入做法:
(1).找插入位置
(2).数组元素a[i]--a[n]后移一个位置
(3).a[i]插入数,表长度增1
代码:
bool ListInsert(List &L,int i,ElemType e)
{ int j;
if (i<1 || i>L->length+1)
return false; //参数错误时返回false
i--; //将顺序表逻辑序号转化为物理序号
for (j=L->length;j>i;j--) //将data[i..n]元素后移一个位置
L->data[j]=L->data[j-1];
L->data[i]=e; //插入元素e
L->length++; //顺序表长度增1
return true; //成功插入返回true
}
7.删除元素
bool ListDelete(List &L,int i,ElemType &e)
{
if (i<1 || i>L->length) //删除位置不合法
return false;
i--; //将顺序表逻辑序号转化为物理序号
e=L->data[i];
for (int j=i;jlength-1;j++)
L->data[j]=L->data[j+1];
L->length--; //顺序表长度减1
return true;
}
(3)顺序存储结构的优缺点:
优点
逻辑相邻,物理相邻
无须为表示表中元素之间的顺序关系增加额外的存储空间
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素(除操作在表尾的位置进行外)
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
3.链表
1.链表结构和定义
结构:
节点 = 数据元素 + 指针
1.数据元素:存放数据
2.指针:存放该节点下一个元素的存储位置
定义:
这样的链接表可以存放线性表,称之为链表。
2.线性表基本运算在单链表实现
1.初始化线性表
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
Status InitList_L(LinkList &L){
L=new LNode;
L->next=NULL;
return OK;
}
2.建立单链表
头插法:
新增节点从链表头部插入
代码:
void CreateListF(LinkList &L,ElemType a[],int n)
{
int i;
L=new LNode;
L->next=NULL;
LinkList nodePtr;
for(i=0;idata=a[i];
nodePtr->next=L->next;
L->next= nodePtr;
}
}
尾插法:
新增节点从链表尾部插入
代码:
void CreateListR(LinkList &L,ElemType a[],int n)
{
int i;
LinkList nodePtr,tailPtr;
L=new LNode;
L->next=NULL;
tailPtr=L;//尾指针
for(i=0;idata=a[i];
rearPtr->next=s;//尾部插入新结点
rearPtr=s;
}
nodePtr->next=NULL;
}
3.销毁线性表
void DestroyList(LinkList &L)
{
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
}
4.输出线性表DispList(L)
void DispList(LinkList L)//输出链表
{
LNode* p;
p = L->next;
if (p == NULL)
{
cout << "空链表!";
return;
}
while (p->next)
{
cout << p->data << " ";
p = p->next;
}
cout << p->data;
}
5.查找数据元素
bool GetElem(LinkList L,int i,ElemType &e)
{ int j=0;
LinkList p=L; //p指向头节点,j置为0(即头节点的序号为0)
while (jnext;
}
if (p==NULL) //不存在第i个数据节点,返回false
return false;
else //存在第i个数据节点,返回true
{ e=p->data;
return true;
}
}
6.插入数据元素
在L中第i个元素之前插入数据元素e
bool ListInsert(LinkList &L,int i,ElemType e)
{
int j=0;
LinkList p=L,s;
while(p&&jnext;
}
if(p==NULL) return false; //未找到第i-1个结点
s=new LNode;
s->data=e;
s->next=p->next; //插入p后面
p->next=s;
return true;
}
7.删除数据元素
在单链表中删除第 i 个结点的基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针。
bool ListDelete_L(LinkList &L,int i,ElemType &e)
{
int j=0;
LinkList p=L,s,q;
while(p&&jnext;j++;
}
if(p==NULL) return false;
q=p->next; //第i个位置
if(q==NULL) return false;
e=q->data;
p->next=q->next;//改变指针关系,删除
delete q;
return true;
}
3.链表中应注意
(1).遍历链表过程中务必考虑指针是否为空,尤其p->next或p->data前务必考虑p是否为空
(2).链表变化,经常要重构。重构做法:
p=L->next;L->next=NULL;
(3).链表做删除时候,务必考虑链表已经空的情况
(4).链表做插入时候,注意要知道插入点的前驱指针在哪里,可以通过pre->next来获取。
(5).要保留指针后继,可设计nextptr=p->next,中间p变化,再p=nextptr。
(6).链表设计,务必画图,了解指针目前状态。
4.有序表
1.定义
所谓有序表,是指这样的线性表,其中所有元素以递增或递减方式有序排列。
2.有序顺序表的插入
void InsertSq(SqList &L,int x)
{
for(int j = L->length; j > 0; j--)
{
if (x >= L->data[j-1]) //找
{
L->data[j] = x;
break;
}
L->data[j] = L->data[j-1];//移动,边移边找
L->data[j-1] = x;//保证第一个数据插入
}
L->length++;
}
3.有序单链表的插入
void ListInsert(LinkNode &L,ElemType e)
{ LinkNode pre=L,p;
while (pre->next!=NULL && pre->next->datanext; //查找插入结点的前驱结点*pre
p=new LinkNode;
p->data=e; //创建存放e的数据结点*p
p->next=pre->next; //在*pre结点之后插入*p结点
pre->next=p;
}
4.两个有序表的合并
void MergeList(LinkList& L1, LinkList L2)//合并链表
{
LinkList ptr1, ptr2, tailptr;
ptr1 = L1->next;
L1->next = NULL;
ptr2 = L2->next;
tailptr = L1;
while (ptr1 && ptr2)
{
if (ptr1->data < ptr2->data)
{
tailptr->next = ptr1;
tailptr = ptr1;
ptr1 = ptr1->next;
}
else if (ptr1->data > ptr2->data)
{
tailptr->next = ptr2;
tailptr = ptr2;
ptr2 = ptr2->next;
}
else
{
tailptr->next = ptr2;
tailptr = ptr2;
ptr2 = ptr2->next;
ptr1 = ptr1->next;
}
}
if (ptr1 != NULL)
tailptr->next = ptr1;
if (ptr2 != NULL)
tailptr->next = ptr2;
}
5.双链表
1.双链表的建立
头插法:
void CreateListF(DLinkNode *&L,ElemType a[],int n)
{ DLinkNode *s; int i;
L=(DLinkNode *)malloc(sizeof(DLinkNode)); //创建头结点
L->prior=L->next=NULL; //前后指针域置为NULL
for (i=0;idata=a[i]; //创建数据结点*s
s->next=L->next; //将*s插入到头结点之后
if (L->next!=NULL) //若L存在数据结点,修改前驱指针
L->next->prior=s;
L->next=s;
s->prior=L;
}
}
尾插法:
void CreateListR(DLinkNode *&L,ElemType a[],int n)
{ DLinkNode *s,*r;
int i;
L=(DLinkNode *)malloc(sizeof(DLinkNode)); //创建头结点
L->prior=L->next=NULL; //前后指针域置为NULL
r=L; //r始终指向尾结点,开始时指向头结点
for (i=0;idata=a[i]; //创建数据结点*s
r->next=s;
s->prior=r; //将*s插入*r之后
r=s; //r指向尾结点
}
r->next=NULL; //尾结点next域置为NULL
}
2.双链表中结点的插入和删除
在p结点之后插入结点s
操作语句:
s->next = p->next
p->next->prior = s
s->prior = p
p->next = s
删除*p结点之后的一个结点
操作语句:
p->next->next->prior = p
p->next = p->next->next
6.循环链表
循环链表是另一种形式的链式存储结构形式。
循环单链表:将表中尾结点的指针域改为指向表头结点,整个链表形成一个环。由此从表中任一结点出发均可找到链表中其他结点。
与单链表区别:
1.从循环链表中的任何一个结点的位置都可以找到其他所有结点,而单链表做不到;
2.循环链表中没有明显的尾端
1.2.谈谈你对线性表的认识及学习体会。
优点:
1.节省存储空间
2.存取表中任一位置的元素时方便快捷
3.用链表表示线性表不受空间限制,在节点的插入、删除方便,不用大量移动数据;
缺点:
1.当长度变化大时,难以确定存储空间的容量
2.顺序存储结构会造成存储空间的“碎片”。
2.顺序存储结构时,插入和删除操作需要移动大量的元素。
学习体会:
顺序存储和链式存储具有不同的优缺点,用线性表操作可以更加方便。
2.PTA实验作业
2.1
------------恢复内容开始------------
0.PTA得分截图
1.本周学习总结
1.1 总结线性表内容
1.线性表定义
线性表是具有相同特性的数据元素的一个有序序列。
2.顺序表
(1)结构体定义
#define MAXSIZE 50//最大长度
typedef int ElemType;
typedef struct
{
ElemType *elem;//指向数据元素的基地址
int length; //线性表当前长度
} SqList;
或者
#define MAXSIZE 50//最大长度
typedef int ElemType;
typedef struct
{
ElemType data[MaxSize];//存放顺序表中的元素
int length; //顺序表的长度
} SqList;
建议:typedef SqList *List;
(2)顺序表的基本操作
1.初始化顺序表
typedef int ElemType;
typedef struct
{
ElemType data[MaxSize];//存放顺序表中的元素
int length; //顺序表的长度
} List;
typedef List *SqList;
void CreateList(SqList &L,int n)
{
int i;
L=new List;
L->length=n;
for(i=0;i>L->data[i];
}
}
2.销毁线性表DestroyList(L)
结果是释放线性表L占用的内存空间。
void DestroyList(SqList *&L)
{
free(L);
}
3.输出线性表DispList(L)
void DispList(List L)
{ int i;
if (ListEmpty(L)) return;
for (i=0;ilength;i++)
cout<data[i]<<" ";
}
4.获取L中第i个元素
返回L中第i个元素的值,存放在e中。1≤i≤ListLength(L)
bool GetElem(List L,int i,ElemType &e){
if (i<1 || i>L->length)
return false;
e=L->data[i-1];
return true;
}
5.按元素值查找
int LocateElem(List L, ElemType e)
{
for(int i=0; ilength;i++)
if(L->data[i]==e)
return i+1; //返回元素的逻辑位序
return 0;
}
6.插入元素
插入做法:
(1).找插入位置
(2).数组元素a[i]--a[n]后移一个位置
(3).a[i]插入数,表长度增1
代码:
bool ListInsert(List &L,int i,ElemType e)
{ int j;
if (i<1 || i>L->length+1)
return false; //参数错误时返回false
i--; //将顺序表逻辑序号转化为物理序号
for (j=L->length;j>i;j--) //将data[i..n]元素后移一个位置
L->data[j]=L->data[j-1];
L->data[i]=e; //插入元素e
L->length++; //顺序表长度增1
return true; //成功插入返回true
}
7.删除元素
bool ListDelete(List &L,int i,ElemType &e)
{
if (i<1 || i>L->length) //删除位置不合法
return false;
i--; //将顺序表逻辑序号转化为物理序号
e=L->data[i];
for (int j=i;jlength-1;j++)
L->data[j]=L->data[j+1];
L->length--; //顺序表长度减1
return true;
}
(3)顺序存储结构的优缺点:
优点
逻辑相邻,物理相邻
无须为表示表中元素之间的顺序关系增加额外的存储空间
可随机存取任一元素
存储空间使用紧凑
缺点
插入、删除操作需要移动大量的元素(除操作在表尾的位置进行外)
预先分配空间需按最大空间分配,利用不充分
表容量难以扩充
3.链表
1.链表结构和定义
结构:
节点 = 数据元素 + 指针
1.数据元素:存放数据
2.指针:存放该节点下一个元素的存储位置
定义:
这样的链接表可以存放线性表,称之为链表。
2.线性表基本运算在单链表实现
1.初始化线性表
typedef struct LNode{
ElemType data; //数据域
struct LNode *next; //指针域
}LNode,*LinkList;
Status InitList_L(LinkList &L){
L=new LNode;
L->next=NULL;
return OK;
}
2.建立单链表
头插法:
新增节点从链表头部插入
代码:
void CreateListF(LinkList &L,ElemType a[],int n)
{
int i;
L=new LNode;
L->next=NULL;
LinkList nodePtr;
for(i=0;idata=a[i];
nodePtr->next=L->next;
L->next= nodePtr;
}
}
尾插法:
新增节点从链表尾部插入
代码:
void CreateListR(LinkList &L,ElemType a[],int n)
{
int i;
LinkList nodePtr,tailPtr;
L=new LNode;
L->next=NULL;
tailPtr=L;//尾指针
for(i=0;idata=a[i];
rearPtr->next=s;//尾部插入新结点
rearPtr=s;
}
nodePtr->next=NULL;
}
3.销毁线性表
void DestroyList(LinkList &L)
{
LinkList p;
while(L)
{
p=L;
L=L->next;
delete p;
}
}
4.输出线性表DispList(L)
void DispList(LinkList L)//输出链表
{
LNode* p;
p = L->next;
if (p == NULL)
{
cout << "空链表!";
return;
}
while (p->next)
{
cout << p->data << " ";
p = p->next;
}
cout << p->data;
}
5.查找数据元素
bool GetElem(LinkList L,int i,ElemType &e)
{ int j=0;
LinkList p=L; //p指向头节点,j置为0(即头节点的序号为0)
while (jnext;
}
if (p==NULL) //不存在第i个数据节点,返回false
return false;
else //存在第i个数据节点,返回true
{ e=p->data;
return true;
}
}
6.插入数据元素
在L中第i个元素之前插入数据元素e
bool ListInsert(LinkList &L,int i,ElemType e)
{
int j=0;
LinkList p=L,s;
while(p&&jnext;
}
if(p==NULL) return false; //未找到第i-1个结点
s=new LNode;
s->data=e;
s->next=p->next; //插入p后面
p->next=s;
return true;
}
7.删除数据元素
在单链表中删除第 i 个结点的基本操作为:找到线性表中第i-1个结点,修改其指向后继的指针。
bool ListDelete_L(LinkList &L,int i,ElemType &e)
{
int j=0;
LinkList p=L,s,q;
while(p&&jnext;j++;
}
if(p==NULL) return false;
q=p->next; //第i个位置
if(q==NULL) return false;
e=q->data;
p->next=q->next;//改变指针关系,删除
delete q;
return true;
}
3.链表中应注意
(1).遍历链表过程中务必考虑指针是否为空,尤其p->next或p->data前务必考虑p是否为空
(2).链表变化,经常要重构。重构做法:
p=L->next;L->next=NULL;
(3).链表做删除时候,务必考虑链表已经空的情况
(4).链表做插入时候,注意要知道插入点的前驱指针在哪里,可以通过pre->next来获取。
(5).要保留指针后继,可设计nextptr=p->next,中间p变化,再p=nextptr。
(6).链表设计,务必画图,了解指针目前状态。
4.有序表
1.定义
所谓有序表,是指这样的线性表,其中所有元素以递增或递减方式有序排列。
2.有序顺序表的插入
void InsertSq(SqList &L,int x)
{
for(int j = L->length; j > 0; j--)
{
if (x >= L->data[j-1]) //找
{
L->data[j] = x;
break;
}
L->data[j] = L->data[j-1];//移动,边移边找
L->data[j-1] = x;//保证第一个数据插入
}
L->length++;
}
3.有序单链表的插入
void ListInsert(LinkNode &L,ElemType e)
{ LinkNode pre=L,p;
while (pre->next!=NULL && pre->next->datanext; //查找插入结点的前驱结点*pre
p=new LinkNode;
p->data=e; //创建存放e的数据结点*p
p->next=pre->next; //在*pre结点之后插入*p结点
pre->next=p;
}
4.两个有序表的合并
void MergeList(LinkList& L1, LinkList L2)//合并链表
{
LinkList ptr1, ptr2, tailptr;
ptr1 = L1->next;
L1->next = NULL;
ptr2 = L2->next;
tailptr = L1;
while (ptr1 && ptr2)
{
if (ptr1->data < ptr2->data)
{
tailptr->next = ptr1;
tailptr = ptr1;
ptr1 = ptr1->next;
}
else if (ptr1->data > ptr2->data)
{
tailptr->next = ptr2;
tailptr = ptr2;
ptr2 = ptr2->next;
}
else
{
tailptr->next = ptr2;
tailptr = ptr2;
ptr2 = ptr2->next;
ptr1 = ptr1->next;
}
}
if (ptr1 != NULL)
tailptr->next = ptr1;
if (ptr2 != NULL)
tailptr->next = ptr2;
}
5.双链表
1.双链表的建立
头插法:
void CreateListF(DLinkNode *&L,ElemType a[],int n)
{ DLinkNode *s; int i;
L=(DLinkNode *)malloc(sizeof(DLinkNode)); //创建头结点
L->prior=L->next=NULL; //前后指针域置为NULL
for (i=0;idata=a[i]; //创建数据结点*s
s->next=L->next; //将*s插入到头结点之后
if (L->next!=NULL) //若L存在数据结点,修改前驱指针
L->next->prior=s;
L->next=s;
s->prior=L;
}
}
尾插法:
void CreateListR(DLinkNode *&L,ElemType a[],int n)
{ DLinkNode *s,*r;
int i;
L=(DLinkNode *)malloc(sizeof(DLinkNode)); //创建头结点
L->prior=L->next=NULL; //前后指针域置为NULL
r=L; //r始终指向尾结点,开始时指向头结点
for (i=0;idata=a[i]; //创建数据结点*s
r->next=s;
s->prior=r; //将*s插入*r之后
r=s; //r指向尾结点
}
r->next=NULL; //尾结点next域置为NULL
}
2.双链表中结点的插入和删除
在p结点之后插入结点s
操作语句:
s->next = p->next
p->next->prior = s
s->prior = p
p->next = s
删除*p结点之后的一个结点
操作语句:
p->next->next->prior = p
p->next = p->next->next
6.循环链表
循环链表是另一种形式的链式存储结构形式。
循环单链表:将表中尾结点的指针域改为指向表头结点,整个链表形成一个环。由此从表中任一结点出发均可找到链表中其他结点。
与单链表区别:
1.从循环链表中的任何一个结点的位置都可以找到其他所有结点,而单链表做不到;
2.循环链表中没有明显的尾端
1.2.谈谈你对线性表的认识及学习体会。
优点:
1.节省存储空间
2.存取表中任一位置的元素时方便快捷
3.用链表表示线性表不受空间限制,在节点的插入、删除方便,不用大量移动数据;
缺点:
1.当长度变化大时,难以确定存储空间的容量
2.顺序存储结构会造成存储空间的“碎片”。
2.顺序存储结构时,插入和删除操作需要移动大量的元素。
学习体会:
顺序存储和链式存储具有不同的优缺点,用线性表操作可以更加方便。
2.PTA实验作业
2.1区间删除数据
2.1.1代码截图
2.1.2本题PTA提交列表说明。
部分正确:输出的时候尾部多了一个空格,自己逐步调试发现问题
部分正确:线性表为空的时候输出错误,请教同学发现是判断条件错了
2.2有序链表合并
2.2.1代码截图
2.2.2本题PTA提交列表说明。
部分正确:没有考虑链表没有比较完的情况
多种错误:添加链表没有比较完时代码错误,后来回顾了之前的讲解才改正
2.3链表倒数第m个数
2.3.1代码截图
2.3.2本题PTA提交列表说明。
编译错误:有两条语句写错了
部分正确:倒数最后一个数的地方错误,后来请教了同学才改正。
3.阅读代码
3.1反转链表
题目:反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode* dummy=new ListNode(-1);
ListNode* pre=dummy;
dummy->next=head;
for(int i=0;inext;
ListNode* cur=pre->next;
for(int i=m;inext;
cur->next=t->next;
t->next=pre->next;
pre->next=t;
}
return dummy->next;
}
};
3.1.1该题的设计思路
3.1.2该题的伪代码
for i to m-1
结点1pre=结点1的next;
结点2cur=结点1的next;
for i=m to n
临时结点t=结点2的next;
cur->next=t->next;
t->next=pre->next;
pre->next=t;
end for
3.1.3运行结果
3.1.4分析该题目解题优势及难点。
优势:思路简洁清晰,用了所熟悉的头插法,不容易出错,只需遍历一遍。
难点:只能遍历一遍
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
代码:
class Solution {
public ListNode rotateRight(ListNode head, int k) {
// base cases
if (head == null) return null;
if (head.next == null) return head;
// close the linked list into the ring
ListNode old_tail = head;
int n;
for(n = 1; old_tail.next != null; n++)
old_tail = old_tail.next;
old_tail.next = head;
// find new tail : (n - k % n - 1)th node
// and new head : (n - k % n)th node
ListNode new_tail = head;
for (int i = 0; i < n - k % n - 1; i++)
new_tail = new_tail.next;
ListNode new_head = new_tail.next;
// break the ring
new_tail.next = null;
return new_head;
}
}
3.2.1该题的设计思路
3.2.2该题的伪代码
for n to old-tail.next不为空
旧尾结点=旧尾结点的next;
旧尾结点的next=头结点;
end for
for i to n-k%n-1
新尾结点=新尾结点的next;
新头结点=新尾结点的next;
新尾结点的next=null;
end for
return 新头结点
3.2.3运行结果
3.2.4分析该题目解题优势及难点。
优势:闭合成循环链表,方便寻找新表头
难点:新尾部的结点位置不容易找准