有序表的二路归并
Note:看清楚问题的要求,能否破坏原有表LA和LB,也就是归并后产生的新表能否利用LA或LB的空间,若不能破坏的话,那就采用建表的方法新建一个表LC
1.顺序表的归并
分别扫描LA和LB两个有序表,当两个有序表都没有扫描完时产生循环:比较LA、LB的当前元素,将其中较小的元素放入LC中,再从较小元素所在的有序表中取下一个元素。重复这一过程直到LA或LB比较完毕,最后将未比较完的有序表中余下的元素放入LC中。
示意图:
此代码是要求不能破坏原有表LA和LB的:
void merge(SqList *LA,SqList *LB,SqList *&LC)
{
int i=0,j=0,k=0; //i、j、k分别作为LA、LB、LC的下标,k为LC中元素的个数
LC=(SqList *)malloc(sizeof(SqList));
LC->length=0;
while (i<LA->length && j<LB->length)
{
if (LA->data[i]<LB->data[j])
{
LC->data[k]=LA->data[i];
i++;k++;
}
else //LA->data[i]>LB->data[j]
{
LC->data[k]=LB->data[j];
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];
j++;k++;
}
LC->length=k;
}
此代码对是否可以破坏原有表LA和LB无要求:
void merge(SqList A,SqList B,SqList &c){
if(A.length+B.Length>C.maxSize)
return false;
int i=0,j=0,k=0; //i、j、k分别作为A、B、C的下标
while (i<A->length && j<B->length){ //核心:循环,两两比较,小者存放入表C
if(A.data[i]<=B.data[j])
C.data[k++]=A.data[i++];
else
C.data[k++]=B.data[j++];
}
//A或B表中,还剩一个没有比较完的顺序表
while(i<A.length)
C.data[k++]=A.data[i++];
while(j<B.length)
C.data[k++]=B.data[j++];
C.length=k;
return true;
}
2.链表的归并
递增、递增归并成递增(尾插法)
假设有两个按元素值递增次序排列的线性表A、B,均以单链表形式存储。请编写算法将这两个单链表归并为一个按元素值递增次序排列的单链表C,并要求用原来两个单链表的结点存放归并后的单链表
此代码是要求不能破坏原有表LA和LB的:
void merge(LinkNode *LA,LinkNode *LB,LinkNode *&LC)
{
LinkNode *pa=LA->next,*pb=LB->next,*pc,*s;
LC=(LinkNode *)malloc(sizeof(LinkNode)); //创建LC的头结点
pc=LC; //pc始终指向LC的最后一个结点
while (pa!=NULL && pb!=NULL)
{
if (pa->data<pb->data)
{
s=(LinkNode *)malloc(sizeof(LinkNode));//复制pa结点
s->data=pa->data;
pc->next=s;pc=s; //采用尾插法将结点s插入到LC的最后
pa=pa->next;
}
else
{
s=(LinkNode *)malloc(sizeof(LinkNode));//复制pb结点
s->data=pb->data;
pc->next=s;pc=s; //采用尾插法将结点s插入到LC的最后
pb=pb->next;
}
}
while (pa!=NULL)
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //复制pa结点
s->data=pa->data;
pc->next=s;pc=s; //采用尾插法将结点s插入到LC的最后
pa=pa->next;
}
while (pb!=NULL)
{
s=(LinkNode *)malloc(sizeof(LinkNode)); //复制pa结点
s->data=pb->data;
pc->next=s;pc=s; //采用尾插法将结点s插入到LC的最后
pb=pb->next;
}
pc->next=NULL;
}
此代码对是否可以破坏原有表LA和LB无要求:
void merge(LNode *A,LNode *B,LNode *&C)
{
LNode *p=A->next;//p指针来跟踪A的最小值结点
LNode *q=B->next;//q指针来跟踪B的最小值结点
LNode *r;//r始终指向C的末端
C=A;//用A的头结点来做C的头结点,当然用B做也是可以的喽
C->next=null;//
free(B);//那B的头结点就没用用处了,一个光杆司令,把它释放掉
r=C;//此时C是仅有头结点的空链表,头结点即为末端结点
while(p!=NULL&&q!=NULL)//当p与q都不为空时,选取p与q所指结点中的最小值插入C的尾部
//尾插法
if(p->data<=q->data)
{
r->next=p;
p=p->next;
r=r->next;
}
else
{
r->next=q;
q=q->next;
r=r->next;
}
}
r->next=null;
//以下两句直接将还有剩余的链表连接在C的尾部
//类比生活中接链子,是不是仅需从断掉的地方直接连起来就行了
if(p!=NULL)
r->next=p;
if(q!=NUll)
r->next=q;
}
递增、递增归并成递减(头插法)
void mergeR(LNode *A,LNode *B,LNode *&C)
{
LNode *p=A->next;
LNode *p=A->next;
LNode *s;//s用来接收新的结点
C=A;
C->next=null;
free(B);
while(p!=NULL&&q!=NULL)
//头插法
if(p->data<=q->data)
{
s=p;
p=p->next;
s->next=C->next;
C->next=s; //官话:指针的维护
}
else
{
s=q->data;
q=q->next;
s->next=C->next;
C->next=s;
}
}
//下面对于处理其中一个链表的剩余情况就和前面不一样了,只能再来头插蛮
while(p!=NULL)
{
s=p;
p=p-next;
s->next=C-next;
C-next=s;
}
while(q!=NULL)
{
s=q-data;
q=q-next;
s->next=C-next;
C->next=s;
}
}
以上顺序表及单链表的二路归并算法的时间复杂度为:O((Length(LA)+Length(LB))
对于空间复杂度,如果是重新创建表就为:O((Length(LA)+Length(LB)),若产生的新表可以利用原有表LA或LB的空间,则为:O(1).
note:两个长度分别为m,n的有序表A和B采用二路归并算法,最好情况元素的比较次数:min(m,n);最坏情况元素的比较次数为:m+n-1。
逆置问题
1.顺序表(或字符串)逆置
void Reverse(SqList &L){
ElemType temp; //中间变量倒手
for(i=0;i<L.Length/2;i++){
//交换L.data与L.data[L.length-i-1]
temp=L.data[i];
L.data[i]=L.data[L.length-i-1];
L.data[L.length-i-1]=temp;
}
}
另解(左、右指针法): 可设置两个整型变量i和j(i、j为存储数组下标的整型变量,说是指针也无大碍),i指向第一个元素,j指向最后一个元素,边交换i和j所指的元素,边让i和j相向而行,直到相遇(不考虑表长个数是奇数个还是偶数个),代码如下:
for(int i=left,j=right;i<j;++i,--j)//left和right是数组两端元素的下标
{
temp=a[i];
a[i]=a[j];
a[j]=temp;
}
2.单链表的原地逆置,即空间复杂度O(1)
思想(头插法):将头结点摘下,然后从第一个结点开始,依次插入到头结点后面的结点(头插法建立单链表,直到最后一个结点为止)
LinkList Reverse(LinkList L){
Lnode *p,*r; //p为工作指针,r用来保存p的后继,防止断链
p=L->next;
L-next=NULL; //把头结点先摘下来
while(p!=NULL){ //依次将后面的元素结点摘下来
r=p->next; //暂存p的后继
p->next=L->next;//将p结点插入到头结点后面
L->next=p; //头结点与新插入的结点连起来
p=r; //p指向r所指的结点
}
return L;
}
另解:双指针- -我们可以申请两个指针,第一个指针叫 prev,最初是指向 null 的。
第二个指针 curr 指向 head,然后不断遍历 curr。
每次迭代到 curr,都将 curr 的 next 指向 prev,然后 prev 和 curr 同时前进一位。
都迭代完了(curr 变成 null 了),prev 就是最后一个节点了。
struct ListNode *reverseList(struct ListNode* head) {
struct ListNode *prev = NULL;
struct ListNode *curr = head;
while (curr) { //while终止循环的条件是cur==NULL,curr为空则说明prev是最后一个节点
struct ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
顺序表的划分问题
通用性母版:以第一个元素为枢轴,小于等于枢轴的元素在枢轴前面,大于枢轴的元素在枢轴后面,(就算是要求以任意一个位置为枢抽进行划分,那么可以通过交换的方式,还是可以把枢轴放到第一个元素的位置上)
解法一:主要采用交换
void partition(SqList *&L)
{ int i=0,j=L->length-1; //准确来说i,j是存储数组下标的整型变量,说成指针也无大碍
ElemType pivot=L->data[0]; //以data[0]为枢轴
while (i<j) //从区间两端交替向中间扫描,直至i=j为止
{ while (i<j && L->data[j]>pivot)
j--; //从右向左扫描,找一个小于等于pivot的元素
while (i<j && L->data[i]<=pivot)
i++; //从左向右扫描,找一个大于pivot的元素
if (i<j)
swap(L->data[i],L->data[j]);//将L->data[i]和L->data[j]进行交换
}
swap(L->data[0],L->data[i]); //将L->data[0]和L->data[i]进行交换
}
栗子:顺序表为:(3,2,7,1,5,3,4,6,0),利用解法一的思路给出划分过程:
解法二(推荐):实质上是快速排序的划分过程
void partition(SqList *&L)
{ int i=0,j=L->length-1;
ElemType pivot=L->data[0]; //以data[0]为枢轴,放到零时变量里
while (i<j) //从顺序表两端交替向中间扫描,直至i=j为止
{ while (j>i && L->data[j]>pivot)
j--; //从右向左扫描,找一个小于等于pivot的data[j]
L->data[i]=L->data[j]; //找到这样的data[j],放入data[i]处
while (i<j && L->data[i]<=pivot)
i++; //从左向右扫描,找一个大于pivot的记录data[i]
L->data[j]=L->data[i]; //找到这样的data[i],放入data[j]处
}
L->data[i]=pivot; //把枢轴元素放到该放的位置
}
栗子:顺序表为:(3,2,7,1,5,3,4,6,0),利用解法二的思路给出划分过程:
关于删除元素的常见问题
●顺序表中删除所有值为×的元素(x不唯一)
思路:用k记录顺序表L中≠x的元素的个数(就是最后留下的元素个数),边扫描L边统计K,并将≠x的元素向前移动k个位置,最后修改表长L
void delx(sqlist&L,Elemtype x)
{
int k=O;
for(i=0;i<L.length;i++)
if(L.data[i]!≠x){
L.data[K]=L.data[i];
k++; //值≠x的元素增1
}
L.length=k;
}
●在有序顺序表中,删除所有其值重复的元素
思路:直插思想,初始时第一个元素视为非重复的有序表。之后依次判断后面的元素是否与前面非重复的有序表中最后一个元素值相同,若相同继续向后判断,若不同则插入到非重复有序表的最后,直到判断至表尾。
bool DeleteSame(Sqlist &L)
{
if(L.length==0)
return false;
int i,j; //i存储第一个不相同的元素,j为工作指针
for(i=0;j=1;j<L.length;j++)
if(L.data[i]!≠L.data[j]) //查找下一个与上一个元素值不同的元素
L.data[++i]=L.data[j];//找到后,元素前移
L.length=i+1;
return ture;
}
●单链表中删除所有其值为x的元素(x不唯一)
思路:用p从头到尾扫描单链表,pre指向*p结点的前驱。若所指结点的值为x,则删除,并让p移向下一个结点,否则让pre、p指针同步后移一个结点。
void Delx(Linklist &L,Elemtype x){
LNode *p=L->next,*pre=L,*q;//指针q作为一个删除标记结点
while(p!=Null){
if(p->data=x){
q=p;
p=p->next;
pre->next=p; //删除*q结点
free(q);
}
else{
pre=p;
p=p->next;
}
}
}
●在有序(假设是递增的,单链表中,删除其值重复的所有元素
思路:用p扫描递增单链表L,若p指针所指结点的数据域等于其后q指针所指结点的数据域,则删除后者,否则将*p移向下一个结点。
void DelSame(Linklist &L){
LNode *p=L->next,*q; //p为工作指针,q指针是一个删除标记结点(马仔),同时也是在帮p探路
if(p==Null)
return;
while(p->next!=Null){
q=p->next;
if(p->data==q->data) //找到重复值的结点
p->next=q->next; //删除q指针所指结点
free(q);
}
else
p=p->next;
}
}