DS01-线性表

0.展示PTA总分

1.本章学习总结

1.1 总结线性表内容

  • 线性表是最基本、最简单、也是最常用的一种数据结构。线性表(linear list)是数据结构的一种,一个线性表是n个具有相同特性的数据元素的有限序列。即“把所有数据用一根线儿串起来,再存储到物理空间中”。
  • 将数据依次存储在连续的整块物理空间中,这种存储结构称为顺序存储结构(简称顺序表
  • 结构体定义:数据类型及长度
typedef struct {
	int data[maxsize];
	int length;
}SqList;
typedef SqList* List;
  • 顺序表插入
for (i = L->length - 1; i >= 0; i--)//从后面开始找插入位置,不满足元素则往后移,满足则插入
{
    if (x > L->data[i])
    {
        L->data[i + 1] = x;
        L->length++;
        return;
    }
    else
    {
        L->data[i + 1] = L->data[i];
    }
    if (i == 0)
    {
        L->data[i] = x;
        L->length++;
        return;
    }
}
  • 1.先找到插入的位置 2.将数组后移 3.数组的长度增加
  • 顺序表区间删除
int i,j,k;
k = 0;
j = L->length;
L->length = 0;//重构顺序表
for (i = 0; i < j; i++)
{
    if (!(L->data[i]>=min && L->data[i]<=max))//不在删除区间内的,写入重构顺序表
    {
        L->data[k++] = L->data[i];
        L->length++;
    }
}
  • 数据分散的存储在物理空间中,通过一根线保存着它们之间的逻辑关系,这种存储结构称为链式存储结构(简称链表
  • 链表结构定义
typedef struct LNode        //定义单链表结点类型
{
    ElemType data;
    struct LNode *next;     //指向后继结点
} LNode,*LinkList;
  • 头插法
void CreateListF(LinkList& L, int n)
{
	int i;
	LinkList node, tail;
	L = new LNode;
	tail = L;
	tail->next = NULL;
	for (i = 1; i <= n; i++)
	{
		node = new LNode;
		cin >> node->data;
		node->next = L->next;
		L->next = node;
	}

}

  • 尾插法
void CreateListR(LinkList &L, int n)//尾插法建链表,L表示带头结点链表,n表示数据元素个数
{
    int i;
    LinkList node,tail;
    L=new LNode;
    L->next=NULL;
    tail=L;
    for(i=1;i<=n;i++)
    {
        node=new LNode;
        cin >>node->data;
        tail->next=node;
        tail=node;
    }
    tail->next=NULL;
}

  • 链表插入
{
    s=new LNode;
    s->data=e;//插入节点
    s->next=p->next;
    p->next=s;
}

  • 链表结点的删除
{
     q=p->next;//保存删除节点
     p->next=q->next;//将节点连向下一个节点
     delete q;//销毁删除节点
}
  • 有序表:所有元素以递增或递减方式有序排列的线性表
  • 有序表的插入
void InsertSq(SqList& L, int x)
{
	int i, j;
	int temp;
for (i = 0; i < L->length; i++)
	{
		if (x < L->data[i])
		{
			for (j = L->length ; j >i; j--)
			{
				L->data[j] = L->data[j-1];
			}
			L->data[i] = x;
			break;
		}
	}
	if (i == L->length)
	{
		L->data[i] = x;
	}
	L->length++;
}
  • 有序表的合并
while (p1、p2指针都不为空)
{
if(p1数据小于p2)
		将p1数据赋给p,将p插入新链中,并移动p1指针
else if (p2数据小于p1)4
将p2数据赋给p,将p插入新链中,移动p2指针
else (即两数据相等情况)k
将p1或p2赋给p,将p插入新链中,同时移动p1, p2 
}
while (p1仍不为空)
则将L1中剩下的链接上新链
while (p2仍不为空)
则将L2中剩下的链接上新链
void MergeList(LinkList& L1, LinkList L2)//合并链表
{
	LinkList end, p1;
	p1 = L1;
	end = p1;
	L1 = L1->next;
	L2 = L2->next;
	while (L1 && L2)
	{
		if (L1->data < L2->data)
		{
			end->next = L1;
			end = L1;
			L1 = L1->next;
		}
		else if (L1->data > L2->data)
		{
			end->next = L2;
			end = L2;
			L2 = L2->next;
		}
		else
		{
			end->next = L2;
			end = L2;
			L2 = L2->next;
			L1 = L1->next;
		}
	}
	if (L1)
	{
		end->next = L1;
	}
	if (L2)
	{
		end->next = L2;
	}
    L1=p1;
}

  • 循环单链表是另一种形式的链式存贮结构。它的特点是表中最后一个结点的指针域指向头结点,整个链表形成一个环
  • 当循环单链表为空的时候,我们可以有如下表示:
  • 对于非空链表则可以这样表示:
  • 对于循环单链表,我们在遍历的时候的结束条件就不再是p为空的时候结束了,而是p等于头结点的时候遍历才完成。
  • 为了让查找更加方便,在每个节点中再多加了一个指针域,从而让一个指针域指向前一个节点,一个指针域指向后一个节点,就有了循环双链表
  • 循环双链表为空时:
  • 双向链表在初始化时,要给首尾两个节点分配内存空间。成功分配后,要将首节点的prior指针和尾节点的next指针指向NULL,这是之后用来判断空表的条件。同时,当链表为空时,要将首节点的next指向尾节点,尾节点的prior指向首节点。
  • 循环双链表不为空时:

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

  • 上学期期末刚开始学习链表,听的都是懵的.还好这学期又重新讲了一遍,再加上pta的练习,才掌握了链表的基础操作。但是在面对判断使用p还是p->next的时候,还是比较茫然。印象深刻的是,在创建链表的时候,一定要注意最后一个节点的next是否为空。了解了线性表的结构特点和操作方式之后,可以用更高效的方法去处理以前的题目了。

2.PTA实验作业

2.1jmu-ds-链表倒数第m个数

==========

  • 已知一个带有表头节点的单链表,查找链表中倒数第m个位置上的节点。
  • 输入要求:先输入链表结点个数,再输入链表数据,再输入m表示倒数第m个位置。
  • 输出要求,若能找到则输出相应位置,要是输入无效位置,则输出-1。
  • 输入样例:
5
1 2 3 4 5
2
  • 输出样例:
4

2.1.1 代码截图

2.1.2 PTA提交列表及说明

  • 预习时的做法是先计算出总长度,在走总长度—m次,即可。上课时讲了更高效的做法,同时走两个指针,第一个先走m次,然后同时走,当第一个走到最后的时候,第二个恰好走到m的位置。
  • 答案错误:没有考虑位置m无效的情况

2.2 jmu-ds-链表分割

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

2.2.1 代码截图

2.2.2 PTA提交列表及说明

  • 做这题的时候没有想到链表的头插法。。因此做不出,后来知道了尾插头插同时使用,就顺利的做出了
  • 内存超限,第一次做定义了很多指针
  • 答案错误:没有审清题目,就以为是链表走一遍,一个给L1,一个给L2

2.3一元多项式的乘法与加法运算

  • 设计函数分别求两个一元多项式的乘积与和。
  • 输入数据:
4 3 4 -5 2  6 1  -2 0
3 5 20  -7 4  3 1
  • 输出数据:
15 24 -25 22 30 21 -10 20 -21 8 35 6 -33 5 14 4 -15 3 18 2 -6 1
5 20 -4 4 -5 2 9 1 -2 0

2.3.1 代码截图






2.3.2 PTA提交列表及说明

  • 答案错误:空的多项式需要用0 0输出
  • 编译错误:函数没有在主函数之前定义

3.阅读代码

3.1 题目及解题代码

  • 旋转链表
    给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
    示例一:
输入: 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

示例二:

输入: 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

3.1.1 该题的设计思路

先求出链表长度size,若k取余size为空,那么不用旋转了,直接返回head;否则将链表首尾相连形成环形链表,由于k表示尾节点向右移动k%size位,那么头节点向右移动size-k%size位,此时的tail移动size-k%size位到达新头节点的前驱节点,我们仅仅需要保存新头节点,同时断开链表就好了。

3.1.2 该题的伪代码

if(为空链表)return 0;
while(遍历链表,记录长度)
首尾相连,形成环形链表
while(长度--)
{
     使p前进;
}
p节点为新的头节点,顺便断开环形链表

3.1.3 运行结果

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

  • 看到这题时,刚开始的思路是,一个个操作节点,改变节点的值。而该题的快速解法则是:将链表变为循环链表,再改变头节点的位置,最后断开链表即可。
  • 时间复杂度从O(n2)降到O(n)

3.2 题目及解题代码

  • 两两交换链表中的节点
    -给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
    实例:
给定 1->2->3->4, 你应该返回 2->1->4->3.


3.2.1 该题的设计思路

  • 定义两个指针,同时扫描,符合条件就交换,同时将循环指针后移

3.2.2 该题的伪代码

while (1)
{
	当前节点pre指向next的下一个
		下一个节点next指向当前节点pre,这就实现了一次两两互换
		记录当前的pre节点
		if (now->next != NULL)如果后面还有节点
		{
			now 和next分别后移两位
		}
			else没有就break,这是针对节点数为偶数的情况
			if (next != NULL)如果next不是空,即节点数为偶数,此时next对应的为 2,4, 6, 8等等
				pre->next = next;
	else节点数为奇数, 则必然最后一个节点的next为空
}

3.2.3 运行结果

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

  • 时间复杂度:O(N)O(N)
  • 空间复杂度:O(1)O(1)
  • 该题也可以采用递归的方法,写起来难度更高,但是代码相对简洁,力扣中多采用递归和该种方法,递归的时间复杂度和空间复杂度都为O(n),该法时间复杂度和空间复杂度分别为O(n)和O(1)
posted on 2020-03-08 11:04  洪志鸿  阅读(376)  评论(0编辑  收藏  举报