2.11线性表总结与提高
【主要知识点】
线性表的特征:线性表中每个数据元素有且仅有一个直接前驱和一个直接后继,第一个 结点无前驱,最后一个结点无后继。
线性表存储方式:实现线性表在计算机中的存放有顺序存储与链式存储两种方式。
线性表顺序存储(顺序表):采用静态分配方式,借助于 C 语言的数组类型,申请一组连续的地址空间,依次存放表中元素,其逻辑次序隐含在存储顺序之中。因为线性表中元素类型相同(即占用空间相等),如果给定起始地址(即数组名)和元素下标,那么就可以方便地实现表中元素的存取。但当表长度变化时需引起元素的移动,因此较适合表中元素个数 固定的情况。
线性表链式存储(链表):采用动态分配方式,借助于 C 语言的指针类型,动态申请与动态释放地址空间,故链表中的各结点的物理存储可以是不连续的。当表长度变化时仅需适 当变化指针的联接,适合于表中元素个数动态变化。
单链表的操作特点:要特别注意总结体会指针的用法。
⑴顺链操作技术:从“头”开始,访问单链表 L 中结点 i(p 指向该结点)时,由于第 i 个结点的地址在第 i-1 个结点(pre指向该结点,为 p 的前驱)的指针域中存放,查找必须从单链表的“首结点”开始(p=L);通过 p=p-next 并辅助计数器来实现;
⑵指针保留技术:通过对第 i 个结点进行插入、删除等操作时,需要对第 i-1 个结点的指针域进行链址操作(pre->next),因此在处理过程中始终需要维持当前指针 p 与其前驱指针 pre的关系,将这种技术简称为“指针保留技术”。
链表处理中的相关技术:
-
单链表与多重链表的差别在于指针域的个数;
-
一般链表与循环链表的差别在于是否首尾相接,将非空表、空表等多种情况统一处理,以方便运算。
-
判断当前结点 p 是否为表尾:一般链表中,p 结点是表尾的条件是:该结点的后继指针值为空指针即:p->next==NULL,循环链表中,p 结点是表尾结点的条件是:该结点的后继指针值为头指针值即:p->next= = head 。
-
链表的表长度 n 值并未显式保存:由于链表是动态生成的结构,其长度要通过顺链 查找到表尾得到。因此在处理链表时,往往是以当前处理位置结点 p是否为表尾作 为控制条件,而不是以表长度 n 作为控制条件。
【典型题例】
例1 已知顺序表 L 中的数据元素类型为 int。设计算法将其调整为左右两部分,左边 的元素(即排在前面的)均为奇数,右边所有元素(即排在后面的)均为偶数, 并要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。
【问题分析】
初见此题,可能会想到额外申请 1 个顺序表空间,之后依次从顺序表 L 中选 择奇数放入新表前部分,选择偶数放在新表的后半部分。但是题目要求空间复杂度为 O(1), 很显然上述方法是不可行的。既然要求空间复杂度为 O(1),说明只能借助 1 个辅助空间。 分析题目要求,其实只需要将位于表左半部分的偶数与位于表右半部分的奇数通过一个辅助 变量进行交换即可,为此可以设置两个位置指示器 i 和 j,i 初值为 0,j 初值为 L->last,当L->elem[i]为偶数, L->elem[j]为奇数时,则将 L->elem[i] 与 L->elem[j]交换;否则, L->elem[i] 为奇数,i++, L->elem[j]为偶数,j++。这样既可以保证算法的时间复杂度为 O(n),亦可保证空 间复杂度为 O(1)。
【算法描述】
AdjustSqlist(SeqList *L)
{
int i=0,j=L->last;
while(i<j)
{
while(L->elem[i]%2!=0&&i<j)
i++; /*从表的左半部分开始检测,若为奇数,则 i 加 1,直到找到偶数为止*/
while(L->elem[j]%2==0&&i<j)
j--;/* 从表的右半部分开始检测,若为偶数,则 j 减 1,直到找到奇数为止*/
if(i<j)
{
t= L->elem[i];
L->elem[i]= L->elem[j];
L->elem[j]=t; /*交换*/
}
}
}/*end of AdjustSqlist*/
【例2】 算法实现带头结点单链表的就地逆置问题。
【问题分析】
逆置就是使得表中内容由原来的(a1,a2,…,ai-1,ai,ai+1, …,an)变为 (an,an-1,…,ai+1,ai,ai-1, …,a1)。就地逆置就是不需要额外申请结点空间,只需要 利用原有的表中的节点空间。若对顺序表中的元素进行逆置,可以借助于“交换”前后相应 元素;对单链表中的元素进行逆置,则不能按“交换”思路,因为对于链表中第 i 个结点需 要顺链查找第 n-i+1(链表长度为 n)个结点,逆置链表的时间复杂度将达O(n2)。
【算法思路】:逆置后的单链表初始为空,表中的结点不是新生成的,而是从原链表中依次“删除”,再逐个头插入到逆置表中(类同算法 2.5 头查法创建链表)。设逆置链表的初态为空表, “删除”已知链表中的第一个结点,然后将它“插入”到逆置链表的“表头”,即使它成为 逆置链表中“新”的第一个结点,如此循环,直至原链表为空表止。

【算法描述】
void ReverseList(LinkList L) /*逆置带头结点的单链表 L */
{
p=L->next; /* P 为原链表的当前处理结点*/
L->next=NULL; /*逆置单链表初始为空表*/
while(p!=NULL) /*当原链表未处理完*/
{
q=p->next; /*q 指针保留原链表当前处理结点的下一个结点*/
p->next=L->next;
L->next=p; /*将当前处理结点 p 插入到逆置表 L 的表头*/
p=q; /*p 指向下一个待插入的结点*/
} /*end of while*/
}/*end of ReverstList*/
【例3】 建立一个带头结点的线性链表,用以存放输入的二进制数,链表中每个结点的 data 域存放一个二进制位。并在此链表上实现对二进制数加 1 的运算 。
【问题分析】
①建链表:带二进制数可用带头结点的单链表存储,第一个结点存储二进制数 的最高位,依次存储,最后一个结点存储二进制数的最低位。
②二进制加法规则:实现二进 制数加 1 运算,方向从低位往高位找到第一个值为 0 的位,从该位开始,对后面所有低位进 行求反运算。
③链表实现二进制加 1 时,从高位往低位与运算方向正好相反,从第一个结点 开始找,找出最后一个值域为 0 的结点,把该结点值域赋为 1,其后所有结点的值域赋为 0。
④若在链表中未找到值域为 0 的结点,则表示该二进制数各位均为 1,此时,申请一新结点, 值域为 1,插入到头结点与原链表的第一个结点之间,成为新链表的第一个结点,其后所有结点的值域赋为 0。
【算法描述】
void BinAdd(LinkList l) /*用带头结点的单链表 L 存储二进制数,实现加 1 运算*/
{
Node *q,*r, *s;
q=l->next;
r=l;
while(q!=NULL) /*查找最后一个值域为 0 的结点*/
{
if(q->data == 0)
r = q;
q = q->next;
}
if (r != l)
r->data = 1; /*将最后一个值域为 0 的结点的值域赋为 1*/
else /*未找到值域为 0 的结点*/
{
s=(Node*)malloc(sizeof(Node)); /*申请新结点存放最高进位*/
s->data=1; /*值域赋为 1*/
s->next=L->next;
L->next = s; /*插入到头结点之后*/
r = s;
}
r = r->next;
while(r!=NULL) /*将后面的所有结点的值域赋为 0*/
{
r->data = 0;
r = r->next;
}
}/*BinAdd 结束*/

被折叠的 条评论
为什么被折叠?



