0.PTA得分截图

1.本周学习内容总结

1.1总结线性表内容

顺序表结构体定义及创建

typedef int ElemType;
typedef struct
{
ElemType data[MaxSize]; //存放顺序表元素
int length:  //存放顺序表的长度
} List;
typedef List* SqList;
void CreateList(SqList &L .int n)//创建顺序表
{
  inti;
  L=new List; 
  L->length=n;
  for(i=0;i<n;i++) cin>>L->data[i];
}

顺序表插入元素e

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
}

顺序表删除元素e

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;j<L->length-1;j++)
  L->data[j]=L->data[j+1];
  L->length--;//顺序表长度减1
  return true;
}

链表的结构体定义

  • 链表的节点由数据元素及指针组成,其中数据元素存放数据,指针存放该节点下一个元素的存储位置
typedef struct LNode
{
ElemType   data;//数据域
struct LNode  *next;//指针域
} LNode, *LinkList;

头插法创建单链表

  • 具体操作:
    1.声明一个指针变量并初始化空链表L。
    2.让L的头节点的指针指向NULL,即建立一个带头节点的单链表
    3.循环:为新节点nodePtr动态申请内存空间并给其赋值;使新节点指向原来头节点的后面一个节点,头节点指向新节点。

void CreateListF(LinkList &L,ElemType a[],int n)
{
  int i;
  LinkList nodePtr;
  L=new LNode; 
  L->next=NULL;
  
  for(i=0;i<n;i++)
{
  nodePtr=new LNode;
  nodcPtr->data=a[i];
  nodePtr->next=L->next;
  L->next= nodePtr;
}
}

尾插法创建单链表

  • 具体操作:
    1.为头节点开辟新空间并使尾指针tailPtr指向头节点L。
    2.生产新节点nodePtr并将数据域赋值想要插入的数据。
    3.将新建的空间地址的指针变量nodePtr的值赋值给尾指针指向的地址,尾指针指向的地址就变成了新插入节点的地址。
void CreateListR(LinkList &L,ElemType a[],int n)
{
  int i;
  LinkList nodePtr ,ailPtr;
  L=new LNode;
  L->next=NULL;
  tailPtr=L;//尾指针
  for(i=0;i<n;i++)
{
  nodePtr=new LNode;
  nodePtr->data=a[i];
  tailPtr->next=nodePtr;//尾部插入新结点
  tailPtr=nodePtr; 
}
  nodePtr->next=NULL;
}

单链表插入数据元素

bool ListInsert(LinkList &L,int i,ElemType x)//在L中第i个元素之前插入x
{
      int j=0;
      LinkList p=L,s;
      while(p&&j<i-1)
{
      j++;
      p=p->next; 
}
      if(p==NULL) return false;
      s=new LNode;  
      s->data=x;
      s->next=p->next; //插入p后面
      p->next=s;
      return true;
}

单链表删除数据元素

bool ListDelete L(LinkList &L,int i,ElemType &e)
{
  int j=0;
  LinkList p=L,s,q;
  while(p&&j<i-1)
{
  p=p->next;
  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;
}

有序表插入数据元素

首先构造一个只含头结点和首结点的有序单链表(只含一个数据结点的单链表一定是有序的)。然后扫描单链表L余下的结点(由p指向),在有序单链表中通过比较找插人结点p的前驱结点(由pre指向它),在pre结点之后插人p结点,直到p==NULL为止。

void ListInscrt(LinkNode &L,ElcmType e)
{
  LinkNode pre=L, p;

  while (pre->next!=NULL && pre->next->data<e)
{
  pre=pre->next; //查找插入结点的前驱结点*pre
}
  p=new LinkNode;
  p->data=e;  //创建存放e的数据结点*p
  p->next= pre->next;//在 *pre结点之后插入*p结点
  pre->next =p;
  p=q;
}

有序表的二路归并算法

  • 其过程是分别扫描LA和LB两个有序表,当两个有序表都没有扫描完时循环:比较LA、LB的当前元素,将其中较小的元素放人LC中,再从较小元素所在的有序表中取下一个元素。重复这一过程直到LA或LB比较完毕,最后将未比较完的有序表中余下的元素放人LC中。
  • 采用顺序表存放有序表时的二路归并算法:
void UnionList(SqList * LA,SqList * LB,SqList * &LC)
{
  int i=0,j=0,k=0;//ij分别为LA、LB的下标,k为LC中元素的个数
  LC= (SqList * )malloc sizeof(SqList));//建立有序顺序表LC
  while (i<LA-> length &.& j< LB - length)
{ 
    if (LA-> data[]<LB-> data[])
  {
    LC-> data[k]= LA-> data[i];
    i++;k++;
  }
    else            //LA-> data[i]> LB-> data[i]
  { 
    LC-> data[k]= LB -> data[i];
    j++;k++;
  }
}
  while (i< LA -> length)//LA 尚未扫描完,将其余元素插人LC中
{
  LC-> data[k]=LA-> data[i];
  i++;k++;
}
  while (j< LB-> length)  //LB 尚未扫描完,将其余元素插人LC中
{
  LC- > data[k]=LB -> data[];
  j++;k++ ;
}
  LC-> length= k;
}

  • 采用单链表存放有序表时的二路归并算法:
void UnionListl(LinkNode * LA, LinkNode * LB, LinkNode * &LC)
{
  LinkNode * pa=LA-> next, * pb=LB ->next,*r,*s;
  LC= (LinkNode*)malloc( sizeof( LinkNode));  //创建 LC的头结点
  r=LC;  //r始终指向LC的尾结点
while (pa!= NULL && pb!= NULL)
{
    if (pa-> data<pb > data)
  {
   s= (LinkNode* )malloc sizeof(LinkNode)); //复制 pa所指结点
   s->data= pa -> data;
   r-> next=s;
   r=s;                  //将s结点插人到LC中
   pa= pa -> next;
  }
   else
  {
   s= (LinkNode * )malloc( sizeof(LinkNode)); //复 制pb所指结点
   s-> data=pb -> data;
   r -> next=s;
   r=s;            //将s结点插人到LC中
   pb=pb -> next;
  } 

while (pa!= NULL)
{ 
  s= (LinkNode * )malloc( sizeof(LinkNode);//复制pa所指结点
  s-> data=pa -> data;
  r-> next= s;
  r=s;                  //将s结点插人到LC中
  pa=pa-> next;
}

while (pb!= NULL)
{
  s= (LinkNode * )malloc( sizeof(LinkNode)); //复制pb所指结点
  s-> data=pb -> data;
  r -> next=s;
  r=s;            //将s结点插人到LC中
  pb=pb -> next;
}
r->next= NULL;//尾结点的next城置空
}

双链表的结构体定义

  • 在双链表中,由于每个结点既包含一个指向后继结点的指针,又包含一个指向前驱结点的指针,所以当访问过一个结点后既可以依次向后访问每一个结点,也可以依次向前访问每一个结点。因此与单链表相比,双链表中访问一个结点的前、后结点更方便。
typedef struct DNode
{
ElemType data;//存放元素值
struct DNode*prior;//指向前驱结点
struct DNode*next;//指向后继结点
}DLinkNode;//双链表的结点类型

头插法建立双链表

void CreateListF(DLinkNode *&L,ElemTypea[], int n)//含有n个元素的数组a创建带头结点的双链表L
{ 
  DLinkNode *s; int i;
  L=(DLinkNode *)malloc(sizeof(DLinkNode)); //创建头结点
  L->prior=L->next=NULL; //前后指针域置为NULL
  for (i=0;i<n;i++) //循环建 立数据结点
 { 
  s=(DLinkNode *)malloc(sizeof(DLinkNode));
  s->data=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,ElemTypea[], intn)//含有n个元素的数组a创建带头结点的双链表L
{ 
  DLinkNode *s,*r;
  int 1;
  L=(DL inkNode * )malloc(sizeof(DLinkNode)); //创建头结点
  L->prior=L->next=NULL; //前后指针域置为NULL
  r=L; //r始终 指向尾结点,开始时指向头结点
  for (i=0;i<n;i++)  //循环建 立数据结点
  {
    s=(DLinkNode * )malloc(sizeof(DLinkNode));
    s->data=a[i];  //创建数据结点*s
    r->next=s;
    s->prior=r;//将*s插入*r之后
    r=s;//r指向尾结点
  }
  r->next=NULL;//尾结点next域置为NULL
}

循环链表

  • 循环链表是另一种形式的链式存储结构,有循环单链表和循环双链表两种类型。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环。它无须增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
  • 在单链表中,从一已知结点出发,只能访问到该结点及其后续结点,无法找到该结点之前的其它结点。而在单循环链表中,从任一结点出发都可访问到表中所有结点。
  • 循环链表中没有空指针域,也没有明显的尾端,涉及遍历操作时,其终止条件就不再是像非循环链表那样判别p或p->next是否为空,而是判别它们是否等于某一指定指针,如头指针或尾指针等,例p所指节点为尾节点的条件是:p->next=L。

1.2谈谈你对线性表的认识及学习体会

  • 线性表,从名字可以看出来,是具有像线一样的性质的表。它是零个或多个数据元素的有限序列,有顺序存储和链式存储两种形式。也意味着,元素之间是有序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后继,其他每个元素有且只有一个前驱和后继。线性表强调有限,元素个数是有限的。学习线性表,必然要学会各种基本操作,增删查改等,特别是链式表的基本操作,它不像顺序表一样是一片连续的区域,逻辑清晰,它实际操作起来总感觉有点绕,有时候脑子会短路,不明白那行代码的意思。

2.PTA实验作业

2.1.题目1:jmu-ds-区间删除数据

2.1.1代码截图


2.1.2本题PTA提交列表说明

多种错误:两种错误分别是格式错误和答案错误,没有控制好尾部不带空格的问题。
答案错误:调整格式输出,在for循环里面让最后一个数据独立输出不带空格,提交就变成答案错误。
答案错误:后来发现删除后没有修改链表长度,应该加上L->length=j,结果只过了全部删除这一测试点。
部分正确:两次修改提交都是部分正确,没有能删除在区间内的数据,最后不把输出线性表为空和输出数据用if-else语句连起来就对了。

2.2.题目2:jmu-ds-链表倒数第m个数

2.2.1代码截图

2.2.2本题PTA提交列表说明

段错误:开始没有给两个指针变量初始化,全部测试点都是段错误。
部分正确:加上了ptr1=ptr2=L这条语句,过了两个测试点,测试点2位置无效依然是段错误。
部分正确:改了一下flag,当ptr1为空时将其置为0,则flag==1时两指针一起移动,结果发现后两个测试点过了,但是测试点0却变成答案错误。
部分正确:后来还是改回来,反复修改提交多次总是有测试点过不了。
部分正确:最后将头节点分别指向俩指针,并增加了判断L->next是否为空且m是否是有效位置才完全正确。

2.3.题目3:jmu-ds-有序链表合并

2.3.1代码截图


2.3.2本题PTA提交列表说明

部分正确:第一次提交只对了空表的测试点,其他两个测试点提示内存超限。
部分正确:然后发现while循环里面两指针都没有继续移动,分别加上ptr1=ptr1->next和ptr2=ptr2->next,结果内存超限都变成了答案错误。
答案错误:因为插入的时候用了尾插法,于是在最后将尾指针指向空,结果全都错了。
部分正确:前面没有考虑到链表还有剩余数据的情况,所以加上两个while循环判断链表是否有剩余数据,若有则继续插入。

3.阅读代码

3.1 单链表反转 (leetcode 206)

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
struct ListNode* reverseList(struct ListNode* head) {
	struct ListNode* cur = head;
	struct ListNode* prev = NULL;
	while (cur != NULL) {
		struct ListNode* next = cur->next;
		cur->next = prev;
		prev = cur;
		cur = next;
	}
	return prev;
}

3.1.1 该题的设计思路

重新开辟一条链表,然后依次取下原链表的每个结点头插入到新的链表中,新的链表就是逆置的结果。其中时间复杂度为O(n),空间复杂度为O(n)。

3.1.2 该题的伪代码

定义两个指针,一个指针cur指向链表头结点,另一个指针prev设置为空
while(cur不为空)
{
记录下指针cur后面的结点next;
将指针cur的 next 置为指针prev;
然后更改指针cur为指针next;
指针prev改为指针cur;
}
end while
返回prev的地址;

3.1.3 运行结果

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

  • 代码比较精简,只需要一次遍历,时间复杂度为O(n),巧妙地实现了单链表的逆转。难点是写起来比较绕,不容易理清思路。

3.2 查找单链表的中间节点

要求只能遍历一次链表,利用快慢指针。

Node* FindMidNode(Node* pHead)  //找单链表的中间节点
{
       Node* slow = pHead, *fast = pHead;
       while (fast && fast->next && fast->next->next) //预防fast为空,奇数时预防fast的next为空,偶数时保证输出前一个元素
       {
              slow = slow->next;  //slow每次走一步,fast每次走两步,当fast到尾时,slow在中间
              fast = fast->next->next;
       }
       return slow;
}

3.2.1 该题的设计思路

思路:慢指针每一次走一步,快指针每次走两步,快指针走到尾或者倒数第二个节点时,慢指针正好走到中间节点。只需要遍历一次而且不用另外开辟新空间,所以其时间复杂度为O(n),空间复杂度为O(1)。

3.2.2 该题的伪代码

定义一个快指针,一个慢指针,它们都指向头节点
while(fast,fast->next和fast->next->next都不为空)
{
  slow指向下一项;
  fast指向下一项的下一项;
}
end slow;
返回slow的地址;

3.2.3 运行结果

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

  • 快慢指针应用非常灵活,代码量少而功能强大,不需要另行开辟新空间。难点是链表的节点可能是奇数也可能是偶数,循环结束的条件不好判断。
posted on 2020-03-08 19:15  uhoh  阅读(243)  评论(0编辑  收藏  举报