一、基本定义:
线性结构是由n(n>=0)个结点组成的有穷序列;除起始结点没有直接前驱外,其他结点有且仅有一个直接前驱;除终端结点没有直接后继外,其他结点有且仅有一个直接后继;
二、基本操作:
1、顺序表的基本操作:
顺序表是按顺序存储方式构造的线性表,它的一个重要特征是可以实现随即存取,所以在编程的过程中充分利用这个特征,可以大大提高程序的效率,举例如下
例1:编写一个函数从一给定的顺序表A中删除元素值在x到y(x<=y)之间的所有元素,要求以较高的效率来实现
解:从前向后扫描顺序表A,用k记录下元素值在x到y之间的元素的个数(初始k=0)。对于当前扫描的元素,若其值不在x到y之间,则前移k个位置;否则,执行k++ 。这样每个不在x与y之间的元素仅移动一次,所以算法效率高,算法如下
void delnode(SqList &A,Elemtype x,Elemtype y)
{//从一给定的顺序表A中删除元素值在x到y(x<=y)之间的所有元素
int i,k=0;
for(i=0;i<A.len;i++)
{if(A.data[i]>=x&&A.data[i]<=y)
k++; //k增1
else
A.data[i-k]=A.data[i]; //当前元素前移k个位置
}
A.len-=k; //长度减小
}
2、链表的基本操作:
链表的指针操作最为关键,一定要做到熟练运用,特别是指针的初始化,指针的移动等等
,当然熟能生巧,编程多了自然就会运用熟练
例二、实现带头结点的单链表就地逆置
void reverse(LinkList *k)
{//单链表就地逆置
LinkList *p,*q;
p=h->next;
h->next=NULL;
while(p->next!=NULL)
{q=p; //q指向当前结点
p=p->next; //p指向下一个结点
q->next=h->next; //将*q插入到*h之后
h->next=q;
}
}
三、重点难点问题
1、线性表中比较难的问题(可能我不知道其他难的问题,见谅),是集合的交并差操作,这也是线性表的一个运用广泛的操作,很有熟练掌握的必要,现举一个综合程度很高的例子(由于集合的操作涉及到元素的移动,所以这种场合多使用链表存储结构,我也以链表举例)
例三:已知3个单链表A B C中的结点均依元素值自小到大非递减排列(可能存在两个以上值相同的结点),编写算法对A链表进行如下操作:使操作后的链表A中仅留下3个表中均包含的数据元素结点,且没有值相同的结点,并释放所有无用结点。限定算法的时间复杂度位O(m+n+p),其中m n p分别为3个表的长度。
解:先将A中重复的结点删除,然后对A的每个结点判断是否在B和C中。若不在则删除;否则保留,最后的A即为所求。算法如下:
LinkList *(LinkList *ha,LinkList *hb,LinkList *hc)
{//链表A中仅留下3个表中均包含的数据元素结点,且没有值相同的结点,并释放所有无
//用结点
LinkList *pa,*pb,*pc,*q,*r;
q=(LinkList *)malloc(sizeof(LinkList)); //对无头结点链表的处理方法
q->next=ha;
ha=q;
pa=ha->next;
while(pa!=NULL&&pa->next!=NULL)
{q=pa->next;
if(pa->data==q->data)
{pa->next-q->next;
free(q);
}
pa=pa->next;
}
pa->next=NULL;
pa=ha->next;
r=ha;
r->next=NULL; //r始终指向新单链表最后一个结点
pb=hb;
pc=hc;
while(pa!=NULL) //查找均包含的结点
{while(pb!=NULL&&pa->data>pb->data) //*pa是否与hb中结点值相等
pb=pb->next;
while(pc!=NULL&&pa->data>pc->data) //*pa是否与hc中结点值相等
pc=pc->next;
if(pa->data==pb->data&&pa->data==pc->data) //*pa是公共结点
{r->next=pa;
r=pa;
pa=pa->next;
r->next=NULL;
}
else //*pa不是公共结点,则删除
{q=pa;
pa=pa->next; //pa移动到下一个结点
free(q);
}
}
q=ha;
ha=ha->next;
free(q); //删除临时头结点
return ha;
}
2、链表分为带头结点的链表和不带头结点的链表(通常情况下都带头结点),当我们遇到不带头结点的链表的时候就要特别注意,因为它在删除、插入的时候要对首元结点特殊考虑;如果你怕这个“特殊考虑”的麻烦,那你可以另外设一个头结点(如例三中那样)当作链表的临时头结点,最后释放它就可以了,这个就不举例了
3、关于静态链表
静态链表可能是一个容易被人忽视的线性结构,其实它是综合了顺序存储和链式存储的特点,在某些特殊的场合可能发挥很好的作用,举例如下:
例四:大家知道,效率比较高的排序方法有快速排序、堆排序等,这些排序方法都需要在顺序存储结构上实现(从这个方面说需要一种顺序结构);但是排序的过程中需要大量移动记录,当记录很大的时候时间耗费就会很多(从这个方面说需要一种链式结构),这个矛盾可以通过使用静态链表来解决,即除记录采用顺序存储结构外,另外设一个地址向量指向相应记录;例如设r[8]为待排序记录序列,另外设一个adr[8],在开始排序前令adr[i]=i;凡在排序过程中需进行r[i]=r[j]的操作时,均以adr[i]=adr[j]代替;则在排序结束之后,地址向量中的值指示排序后的记录的次序,r[adr[1]]为关键字最小的记录,r[adr[8]]为关键字最大的记录;最后在需要的时候再根据adr的值重排记录的物理位置,这样可以降低了时间的复杂度(至少转移了时间的复杂度,在实际中还是有很多应用的);为了便于理解,下面给出记录重排的算法
void rearrange(SqList L,int adr[])
{//adr给出顺序表L的有序次序,即L.r[adr[i]]是第i小的记录
//本算法按adr重排L.r,使其有序
for(i=1;i<L.length;i++)
if(adr[i]!=i)
{ j=i;
L.r[0]=L.r[i]; //暂存记录L.r[i]
while(adr[j]!=i) //调整L.r[adr[j]]的记录到位直到dar[j]=i为止
{ k=adr[j];
L.r[j]=L.r[k];
adr[j]=j;
j=k;
}
adr[j]=L.r[0];
adr[j]=j; //交换记录到位
}
}
************************************************************************
以上算是对前面几期DS专题活动的总结吧,其中有些算法思想是参考其他资料上面的,有些是自己写的,不对的地方请大家批评指正
可能我的知识面不够广,有什么遗漏的请大家跟贴指出,我这个帖子也只想起一个抛砖引玉的作用,呵呵
线性结构是由n(n>=0)个结点组成的有穷序列;除起始结点没有直接前驱外,其他结点有且仅有一个直接前驱;除终端结点没有直接后继外,其他结点有且仅有一个直接后继;
二、基本操作:
1、顺序表的基本操作:
顺序表是按顺序存储方式构造的线性表,它的一个重要特征是可以实现随即存取,所以在编程的过程中充分利用这个特征,可以大大提高程序的效率,举例如下
例1:编写一个函数从一给定的顺序表A中删除元素值在x到y(x<=y)之间的所有元素,要求以较高的效率来实现
解:从前向后扫描顺序表A,用k记录下元素值在x到y之间的元素的个数(初始k=0)。对于当前扫描的元素,若其值不在x到y之间,则前移k个位置;否则,执行k++ 。这样每个不在x与y之间的元素仅移动一次,所以算法效率高,算法如下
void delnode(SqList &A,Elemtype x,Elemtype y)
{//从一给定的顺序表A中删除元素值在x到y(x<=y)之间的所有元素
int i,k=0;
for(i=0;i<A.len;i++)
{if(A.data[i]>=x&&A.data[i]<=y)
k++; //k增1
else
A.data[i-k]=A.data[i]; //当前元素前移k个位置
}
A.len-=k; //长度减小
}
2、链表的基本操作:
链表的指针操作最为关键,一定要做到熟练运用,特别是指针的初始化,指针的移动等等
,当然熟能生巧,编程多了自然就会运用熟练
例二、实现带头结点的单链表就地逆置
void reverse(LinkList *k)
{//单链表就地逆置
LinkList *p,*q;
p=h->next;
h->next=NULL;
while(p->next!=NULL)
{q=p; //q指向当前结点
p=p->next; //p指向下一个结点
q->next=h->next; //将*q插入到*h之后
h->next=q;
}
}
三、重点难点问题
1、线性表中比较难的问题(可能我不知道其他难的问题,见谅),是集合的交并差操作,这也是线性表的一个运用广泛的操作,很有熟练掌握的必要,现举一个综合程度很高的例子(由于集合的操作涉及到元素的移动,所以这种场合多使用链表存储结构,我也以链表举例)
例三:已知3个单链表A B C中的结点均依元素值自小到大非递减排列(可能存在两个以上值相同的结点),编写算法对A链表进行如下操作:使操作后的链表A中仅留下3个表中均包含的数据元素结点,且没有值相同的结点,并释放所有无用结点。限定算法的时间复杂度位O(m+n+p),其中m n p分别为3个表的长度。
解:先将A中重复的结点删除,然后对A的每个结点判断是否在B和C中。若不在则删除;否则保留,最后的A即为所求。算法如下:
LinkList *(LinkList *ha,LinkList *hb,LinkList *hc)
{//链表A中仅留下3个表中均包含的数据元素结点,且没有值相同的结点,并释放所有无
//用结点
LinkList *pa,*pb,*pc,*q,*r;
q=(LinkList *)malloc(sizeof(LinkList)); //对无头结点链表的处理方法
q->next=ha;
ha=q;
pa=ha->next;
while(pa!=NULL&&pa->next!=NULL)
{q=pa->next;
if(pa->data==q->data)
{pa->next-q->next;
free(q);
}
pa=pa->next;
}
pa->next=NULL;
pa=ha->next;
r=ha;
r->next=NULL; //r始终指向新单链表最后一个结点
pb=hb;
pc=hc;
while(pa!=NULL) //查找均包含的结点
{while(pb!=NULL&&pa->data>pb->data) //*pa是否与hb中结点值相等
pb=pb->next;
while(pc!=NULL&&pa->data>pc->data) //*pa是否与hc中结点值相等
pc=pc->next;
if(pa->data==pb->data&&pa->data==pc->data) //*pa是公共结点
{r->next=pa;
r=pa;
pa=pa->next;
r->next=NULL;
}
else //*pa不是公共结点,则删除
{q=pa;
pa=pa->next; //pa移动到下一个结点
free(q);
}
}
q=ha;
ha=ha->next;
free(q); //删除临时头结点
return ha;
}
2、链表分为带头结点的链表和不带头结点的链表(通常情况下都带头结点),当我们遇到不带头结点的链表的时候就要特别注意,因为它在删除、插入的时候要对首元结点特殊考虑;如果你怕这个“特殊考虑”的麻烦,那你可以另外设一个头结点(如例三中那样)当作链表的临时头结点,最后释放它就可以了,这个就不举例了
3、关于静态链表
静态链表可能是一个容易被人忽视的线性结构,其实它是综合了顺序存储和链式存储的特点,在某些特殊的场合可能发挥很好的作用,举例如下:
例四:大家知道,效率比较高的排序方法有快速排序、堆排序等,这些排序方法都需要在顺序存储结构上实现(从这个方面说需要一种顺序结构);但是排序的过程中需要大量移动记录,当记录很大的时候时间耗费就会很多(从这个方面说需要一种链式结构),这个矛盾可以通过使用静态链表来解决,即除记录采用顺序存储结构外,另外设一个地址向量指向相应记录;例如设r[8]为待排序记录序列,另外设一个adr[8],在开始排序前令adr[i]=i;凡在排序过程中需进行r[i]=r[j]的操作时,均以adr[i]=adr[j]代替;则在排序结束之后,地址向量中的值指示排序后的记录的次序,r[adr[1]]为关键字最小的记录,r[adr[8]]为关键字最大的记录;最后在需要的时候再根据adr的值重排记录的物理位置,这样可以降低了时间的复杂度(至少转移了时间的复杂度,在实际中还是有很多应用的);为了便于理解,下面给出记录重排的算法
void rearrange(SqList L,int adr[])
{//adr给出顺序表L的有序次序,即L.r[adr[i]]是第i小的记录
//本算法按adr重排L.r,使其有序
for(i=1;i<L.length;i++)
if(adr[i]!=i)
{ j=i;
L.r[0]=L.r[i]; //暂存记录L.r[i]
while(adr[j]!=i) //调整L.r[adr[j]]的记录到位直到dar[j]=i为止
{ k=adr[j];
L.r[j]=L.r[k];
adr[j]=j;
j=k;
}
adr[j]=L.r[0];
adr[j]=j; //交换记录到位
}
}
************************************************************************
以上算是对前面几期DS专题活动的总结吧,其中有些算法思想是参考其他资料上面的,有些是自己写的,不对的地方请大家批评指正
可能我的知识面不够广,有什么遗漏的请大家跟贴指出,我这个帖子也只想起一个抛砖引玉的作用,呵呵