2019年统考真题

设线性表L = (a1,a2,a3,...,an)采用头结点的单链表保存,链表中的结点定义如下:

typedef struct node
{
  int data;
  struct node *next;
}NODE;

请设计一个空间复杂度为O(1)且时间上尽可能高效的算法,重新排列L中的各结点,得到线性表L` = (a1,an,a2,..,).要求:

(1)给出算法的基本思想

(2)根据设计思想,采用C或C++语言描述算法,关键之处给出注释

(3)说明你设计的算法时间复杂度。

1.算法的基本设计思想

先观察L = (a1,a2,a3,...,an)和L` = (a1,an,a2,..,),发现L`是由L摘取第一个元素,再摘取倒数第一个元素依次合并而成的。为了方便链表后半段取元素,需要将L后半段原地逆置【题目要求空间复杂度为O(1),因此不能够借助栈】,否则每取到一个结点都需要遍历一次链表。(1)先找出链表的中间结点,为此设置两个指针p和q,指针p每走一步,指针q每次走两步,当指针q到达链尾时,指针p正好在链表的中间结点;(2)然后将L的后半段结点原地逆置。(3)从单链表前后两端依次各取一个结点,按压求重拍。

2.算法实现

void change_List(NODE *h)
{
  NODE *p, *q, *r, *s;
  p = q = h;
  while(q->next != NULL)//寻找中间节点
  {
	  p = p->next;//p走一步
	  q = q->next;
	  if(q->next != NULL)//判断后面是否为空
	  {
		  q = q->next;//q走两步
	  }
  }
  q = p->next;//p所指结点为中间结点,q为后半段链表的首节点
  p->next = NULL;//直接将链表分为两部分
  //将链表后半段倒置,就是换一个方向
  while(q != NULL)
  {
	  r = q->next;//链表的后半段:r指向q指向结点的后面一个结点
	  q->next = p->next;//第一次的时候p->next = NULL,所以此时后半段链表的首节点与第二个结点连接断开
	  p->next = q;//将p挂在p的后面在结合下面一个语句就可以循环你,使链表的后半段的结点连接顺序改变,倒置
	  q = r;
  }
  s = h->next;//s指向前半段的第一个数据结点,即插入点
  q = p->next;//q指向后半段的第一个数据结点
  p->next = NULL;
  while(q != NULL)
  {
	  r = q->next;//将r指向后半段q指向结点的下一个结点
	  q->next = s->next;//改变q指向结点的后继节点,使后继节点变成s指向结点的后继节点
	  s->next = q;//使s指向结点的后继节点变成q指向的那个结点
	  s = q->next;//使s指向q指向结点的后继节点
	  q = r;//使q指向r指向的那个结点,以构成循环
  }
}

演示过程 

 3.第一步找中间结点的时间复杂度为O(n),第二步逆置的时间复杂度为O(n),第三步合并链表的时间复杂度为O(n),所以该算法的时间复杂度为O(n)

完整代码

#include<stdio.h>
#include<malloc.h>
#include<stdlib.h>
typedef struct node
{
  int data;
  struct node *next;
}NODE;
//函数说明
NODE *creat_List(void);
void traverse_List(NODE *h);
void change_List(NODE *h);
int main(void)
{
  NODE *h = NULL;
  h = creat_List();
  printf("链表输出结果:\n");
  traverse_List(h);
  change_List(h);
  printf("改变之后链表的值为:\n");
  traverse_List(h);
  return 0;
}

//创造一个链表
NODE *creat_List(void)
{
  int len;//用来存放有效结点的个数;
  int i;
  int val;//用来临时存放有效节点的值;
  NODE *h = (NODE*)malloc(sizeof(NODE));
  if(NULL == h)
  {
    printf("动态内存分配失败,结束程序!");
	exit(-1);
  }
  NODE *t = h;
  t->next = NULL;
  printf("链表结点的个数:len = ");
  scanf("%d",&len);
  for(i = 0; i<len;++i)
  {
    printf("请输入第%d个结点的值:",i+1);
	scanf("%d",&val);
	NODE *pNew = (NODE*)malloc(sizeof(NODE));
	if(NULL == pNew)
	{
	  printf("结束程序!\n");
	  exit(-1);
	}
	pNew->data = val;
	t->next = pNew;
	pNew->next = NULL;
	t = pNew;
  }
  return h;

}


//遍历输出
void traverse_List(NODE *h)
{
	NODE *p = h->next;
	while(NULL != p)
	{
		printf("%d ",p->data);
		p = p->next;
	}
	printf("\n");
	return;
}

void change_List(NODE *h)
{
  NODE *p, *q, *r, *s;
  p = q = h;
  while(q->next != NULL)//寻找中间节点
  {
	  p = p->next;//p走一步
	  q = q->next;
	  if(q->next != NULL)//判断后面是否为空
	  {
		  q = q->next;//q走两步
	  }
  }
  q = p->next;//p所指结点为中间结点,q为后半段链表的首节点
  p->next = NULL;//直接将链表分为两部分
  //将链表后半段倒置,就是换一个方向
  while(q != NULL)
  {
	  r = q->next;//链表的后半段:r指向q指向结点的后面一个结点
	  q->next = p->next;//第一次的时候p->next = NULL,所以此时后半段链表的首节点与第二个结点连接断开
	  p->next = q;//将p挂在p的后面在结合下面一个语句就可以循环你,使链表的后半段的结点连接顺序改变,倒置
	  q = r;
  }
  s = h->next;//s指向前半段的第一个数据结点,即插入点
  q = p->next;//q指向后半段的第一个数据结点
  p->next = NULL;
  while(q != NULL)
  {
	  r = q->next;//将r指向后半段q指向结点的下一个结点
	  q->next = s->next;//改变q指向结点的后继节点,使后继节点变成s指向结点的后继节点
	  s->next = q;//使s指向结点的后继节点变成q指向的那个结点
	  s = q->next;//使s指向q指向结点的后继节点
	  q = r;//使q指向r指向的那个结点,以构成循环
  }
}

以六个整数型结点为例子结果为:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值