数据结构编程题系列(二)链表

1,092 阅读14分钟

2.链表

2.1.1 尾插算法

​ 1.逆转链表的算法实现(同时考虑用递归方式实现逆序输出)

void reversel(LNode *L)     //头插法
{
    LNode *p = L->next,*q;
    L->next = NULL;
    while(p)
    {
        q = p->next;
        p->next = L->next;
        L->next = p;
        p = q;
    }
}

//顺序表的逆置实现
void reversel(Sqlist &L)
{
    int i,j;
    int temp;
    for(i=0,j=L.length-1;i<j;i++,j--)
    {
        temp = L.data[i];
        L.data[i] = L.data[j];
        L.data[j] = temp;
    }
}

​ 2.将一个升序单链表和一个降序单链表合并为一个有序链表,要求算法时间复杂度为O(m+n)。

​ 降序链表头插法逆序后,两个升序链表合并,时间复杂度为max(m,n),则总时间复杂度为O(m+n).

int merge(LNode *L1,LNode *L2,LNode *&C)       //假设L1为升序 L2为逆序
{
    reversel(L2);                    //L2变升序
    LNode *p = L1->next;
    LNode *q = L2->next;
    LNode *s,*rear;
    C=L1;rear = C;
    L1->next = NULL;
    free(L2);
    while(p&&q)
    {
        if(p->data<=q->data)
        {
         	s = p;p = p->next;
            s->next = NULL;
            rear->next = s;
            rear = rear->next;
        }
        else
        {
            s = q;q = q->next;
            s->next = NULL;
         	rear->next = s;
            rear = rear->next;
        }
    }
    if(p)
        rear->next = p;
    else if(q)
        rear->next = q;    
}

2.1.2 双指针算法

​ 1.编写算法找到两个单链表的公共结点。

​ 大小分别为m,n,(m>n),m-n=k,则L1从L1+k处开始走,L2从L2处开始走,两者相等则当前以及后面的结点为公共结点。

LNode *findCommonNode(LNode *L1,LNode *L2)
{
    //统计L1 L2的长度
    int n1=0,n2=0;
    LNode *p1,*p2;
    p1 = L1->next;
    p2 = L2->next;
    while(p1)
    {
        n1++;p1 = p1->next;
    }
    while(p2)
    {
        n2++;p2 = p2->next;
    }
    int k,i;                 //寻找起点
    if(n1>n2)
    {
        k = n1-n2;
        i=0
        while(p1)
        {
            i++;
            if(i<k+1) p1=p1->next;
            else
                break;
        }
    }
    else
    {
        k = n2-n1;
        i=0
        while(p2)
        {
            i++;
            if(i<k+1) p2=p2->next;
            else
                break;
        }
    }
    while(p1&&p2)
    {
        if(p1->data!=p2->data) 
        {
            p1 = p1->next;p2 = p2->next;
        }
        else
            break;
    }
    return p1;                           
}

​ 2.定位到链表中倒数k个的结点。

​ 双指针。

int findElem(LNode *head,int k)
{
    LNode *p1 = head->next;
    LNode *p = head;
    int i=1;
    while(p1!=NULL)
    {
        p1 = p1->next;
        i++;
        if(i>k)
            p = p->next;
    }
    if(p==head)	return 0;
    else
    {
        cout<<p->data<<endl;
        return 1;
    }
}

​ 3.给定一个链表,

​ (1)判断链表中是否有环。

​ (2)若有环,求环的长度

​ (3)求开始入环的第一个节点

​ 解:1.双指针,一个跑一步,一个跑两步,有环则会相遇,否则快的会跑到null,为尾节点。

int isCircle(LNode *L)
{
    LNode *p1,*p2;
    p1 = L->next;
    p2 = L->next->next;
    while(p2!=NULL)
    {
        if(p1->data!=p2->data)
        {
            p1 = p1->next;
            p2 = p2->next->next;
        }
        else
            break;
    }
    if(p2!=NULL)
        return 1;
    else
        return 0;
}

​ (2).快指针为s,慢指针为q,则当两者相遇时,快指针停在当前位置,慢指针继续走,相遇时走过的步数为环的长度。

int circleLength(LNode *L)
{
    LNode *s,*q;
    s = L->next;
    q = L->next->next;
    while(q!=NULL)
    {
        if(s->data != q->data)
        {
            s = s->next;
            q = q->next->next;
        }
        else
            break;
    }
    if(q==NULL)
        return 0;
    int len=0;
    s = s->next;
    while(s->data!=q->data)
    {
        len++;
        s = s->next;
    }
   return len;
}

​ (3).接1题,两者相遇时,慢指针从当前位置每次一步走,快指针从链表头位置每次一步的走,两者再次相遇的位置节点为入环的第一个节点。

​ 4.请判断一个链表是否为回文链表。

​ 解:思路1:使用栈

​ 思路2:双指针,找到中间节点,将中间节点后面的节点反转,从变形链表的两头向中间靠近,依次对比节点,两指针相遇则成立。

int  dc(LinkedList  h,int n)
//h是带头结点的n个元素单链表,链表中结点的数据域是字符。本算法判断链表是否是中心对称。
{
	char s[];int i=1//i记结点个数, s字符栈
	p=h->next;//p是链表的工作指针,指向待处理的当前元素。
 	for(i=1;i<=n/2;i++)//链表前一半元素进栈。
  		{s[i]=p->data;p=p->next;}
 	i--; //恢复最后的i值
 	if(n%2==1)
 	p=p->next;若n是奇数,后移过中心结点。
 	while(p!=null && s[i]==p->data)
 		{i--;p=p->next;}   //测试是否中心对称。
	if(p==null)return1);//链表中心对称
 	else  return0);       ∥链表不中心对称
}

2.2.2 集合算法

​ 1.求线性表A和B的交集 A B 都为非递减

void intersection(LNode *A,LNode *B,LNode *&L) //求AB的交集
{
    LNode *pa,*pb,*pl;
    pa = A->next;
    pb = B->next;
    L = A;
    L->next = NULL;
    free(B);      //释放B头节点空间
    pl = L;
    while(pa&&pb)
    {
        if(pa->data<pb->data)
            pa = pa->next;
        else if(pb->data<pa->data)
            pb = pb->next;
        else
        {
            pl->next = pa;
            pl = pl->next;
            pa = pa->next;
            pb = pb->next;
        }
    }
    pl->next = NULL;
}

​ 2.求线性表 A B的并集

void union(LNode *A,LNode *B,LNode *&L)    //A B 都是非递减
{
    LNode *pa,*pb,*pl;
    pa = A->next;
    pb = B->next;
    L = A;
    L->next = NULL;
    free(B);      //释放B头节点空间
    pl = L;
    while(pa&&pb)
    {
        if(pa->data<pb->data)
        {
            pl->next = pa;
            pl = pl->next;
            pa = pa->next;
        }
        else if(pb->data<pa->data)
        {
            pl->next = pb;
            pl = pl->next;
            pb = pb->next;
        }
        else
        {
            pl->next = pa;
            pl = pl->next;
            pa = pa->next;
            pb = pb->next;
        }
    }
    if(pa)
        pl->next = pa;
    else if(pb)
        pl->next = pb;
    else
        pl->next = NULL;
}

​ 3.求 A — B (差集)

void differSet(LNode *A,LNode *B,LNode *&L)
{
    LNode *pa,*pb,*p,*pl;
    pa = A->next;
    pb = B->next;
    pl = L = A;
    L->next = NULL;
    free(B);
    while(pa&&pb)
    {
        if(pa->data<pb->data)
        {
            p = pa;
            pa = pa->next;
            p->next = pl->next;
            pl->next = p;
            pl = pl->next;
        }
        else if(pa->data==pb->data)
        {
            pa = pa->next;
            pb = pb->next;
        }
        else
            pb = pb->next;
    }
   while(pa)
   {
       p = pa;
       pa = pa->next;
       p->next = pl->next;
       pl->next = p;
   }
   pl->next = NULL;     
}

​ 4.删除链表中重复的元素

//当链表是有序排列时
void deleteSame(LNode *L)
{
    LNode *p,*t;
    p = L->next;
    while(p->next!=NULL)
    {
        if(p->data==p->next->data)
        {
            t = p->next;
            p->next = p->next->next;
            free(t);
        }
        p = p->next;
    }
}

//当链表是无序排列时  
void deleteSame(LNode *L)
{
    LNode *p,*pre,*pt,*t;
    p = L->next;
    while(p)
    {
        pre= L;
        pt = L->next;
        while(pt)
        {
            if(pt!=p &&pt->data == p->data)
            {
                t = pt;
                pre->next = pt->next;
                pt = pt->next;
                free(t);
            }
            else
            {
             	pre = pt;
           	 	pt = pt->next;   
            }
        }
        p = p->next;
    }
}

2.2.3 链表排序

​ 1.将一个元素插入有序链表中。

void insertElem(LNode *L,ElemType x)
{
    LNode *p,*pre;
    pre = L;p = L-next;
    while(p)
    {
        if(p->data<x)
        {
            pre = p;
            p = p-next;
        }
        break;
    }
    LNode pt = (LNode *)malloc(sizeof(LNode));
    pt->data = x;
    pt->next = pre->next;
    pre->next = pt;
}

​ 2.将链表插入排序

LinkedList LinkListSort(LinkedList list)
//list是不带头结点的线性链表,链表结点构造为data和link两个域,data是数据域,link是指针域。本算法将该链表按结点数据域的值的大小,从小到大重新链接。
{
    p=list->link;   //p是工作指针,指向待排序的当前元素。
    list->link=null;	//假定第一个元素有序,即链表中现只有一个结点。
    while(p!=null)
    {
        r=p->link;    //r是p的后继。
        q=list;
        if(q->data>p->data)//处理待排序结点p比第一个元素结点小的情况。
        {
            p->link=list;
            list=p;//链表指针指向最小元素。
        }
        else//查找元素值最小的结点。
        {
            while(q->link!=null&&q->link->data<p->data)
                q=q->link;
            p->link=q->link;//将当前排序结点链入有序链表中。
            q->link=p;
        }
        p=r;//p指向下个待排序结点。
    }
}

​ 3.对链表进行选择排序

//实现对链表head进行选择排序的算法
typedef struct node {char data; struct node *link; }node;
node *select(node *head)
{node *p,*q,*r,*s; 
p=(node *)malloc(sizeof(node));
   p->link=head; head=p;
while(p->link!=null)
     {q=p->link; r=p;
      while (q->link!=NULL)
         { if (q->link->data<r->link->data) r=q;
           q=q->link;
  }
      if (r!=p) {s=r->link; r->link=s->link; s->link= (p->link); (p->link=s);}
      (p=p->link) ;
     }
p=head; head=head->link; free(p); return(head);
 }

​ 4.设有一头指针为L的带有表头结点的非循环双向链表,其每个结点中除有pred(前驱指针),data(数据)和next(后继指针)域外,还有一个访问频度域freq。在链表被起用前,其值均初始化为零。每当在链表中进行一次Locate(L,x)运算时,令元素值为x的结点中freq域的值增1,并使此链表中结点保持按访问频度非增(递减)的顺序排列,同时最近访问的结点排在频度相同的结点的最后,以便使频繁访问的结点总是靠近表头。

DLinkList  locate(DLinkList L,ElemType x)
//L是带头结点的按访问频度递减的双向链表,本算法先查找数据x,查找成功时结点的访问频度域增1,最后将该结点按频度递减插入链表中适当位置。
{
    DLinkList p=L->next,q;    //p为L表的工作指针,q为p的前驱,用于查找插入位置。
    while (p && p->data !=x) p=p->next; //查找值为x的结点。 
    if (!p) 
    	{printf(“不存在值为x的结点\n”); exit(0);}
  	else { 
        p->freq++;                //令元素值为x的结点的freq域加1 。
        p->next->pred=p->pred;   //将p结点从链表上摘下。 
        p->pred->next=p->next;
        q=p->pred;               //以下查找p结点的插入位置 
        while (q !=L && q->freq<p->freq)
            q=q->pred;
        p->next=q->next;
        q->next->pred=p;	//将p结点插入 
        p->pred=q;
        q->next=p;
    }
    return(p);   //返回值为x的结点的指针
}

2.2.4 链表的分拆

​ 1.将一个链表分解为一个奇数链表和一个偶数链表。

void DisCreat3(LinkedList A)
//A是带头结点的单链表,本算法将其分解成两个带头结点的单链表,A表中含原表中序号为奇数的结点,B表中含原表中序号为偶数的结点。链表中结点的相对顺序同原链表。
{
    i=0;//i记链表中结点的序号。
    //B=(LinkedList)malloc(sizeof(LNode);//创建B表表头。
    B->next=null;    //B表的初始化。
    LinkedList ra,rb;//ra和rb将分别指向将创建的A表和B表的尾结点。
    ra=A;rb=B;
    p=A->next;       //p为链表工作指针,指向待分解的结点。
    A->next=null;    //置空新的A表
    while(p!=null) 
    {
        r=p->next;      //暂存p的后继。
        i++;
        if(i%2==0)      //处理原序号为偶数的链表结点。
        {
            p->next=rb->next;//在B表尾插入新结点;
            rb->next=p;
            rb=p;//rb指向新的尾结点;
        }else//处理原序号为奇数的结点。
        {
            p->next=ra->next;
            ra->next=p;
            ra=p;
        }
        p=r;         //将p恢复为指向新的待处理结点。
    }
}

​ 2.编写函数将一整数序列中所有负数移到所有正数之前

int Rearrange(SeqList a; int n)
//a是具有n个元素的线性表,以顺序存储结构存储,线性表的元素是整数。本算法重排线性表a,使所有值为负数的元素移到所有值为正数的数的前面。
{
    i=0; j=n-1;    //i,j为工作指针(下标),初始指向线性表a的第1个和第n个元素。
    t=a[0];     //暂存枢轴元素。
    while(i<j)
    {
        while(i<j && a[j]>=0)
            j--;  //若当前元素为大于等于零,则指针前移。
        if(i<j)
        {
            a[i]=a[j];
            i++;
        }  //将负数前移。
        while(i<j &&a[i]<0)
            i++;   //当前元素为负数时指针后移。
        if(i<j)
            a[j--]=a[i];      //正数后移。
    }
    a[i]=t;   //将原第一元素放到最终位置。
}

​ 3.给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

​ 链表长度为N,将第N/2个结点作为根节点,并以该节点划分链表为左右两个链表,将左边链表的中间节点作为左节点,右链表的中间节点作为右节点,递归执行。

BTNode* tranTobtree(LNode *L,int n)
{
    if(n/2)
        return NULL;
    else
    {
        int mid,ln,rn;
        if(n%2==0) //偶数
           mid = n/2;
        else      //奇数
            mid = n/2+1;
        ln = mid - 1; 
        rn = n - mid;
        BTNode *b;
        b = (BTNode *)malloc(sizeof(BTNode));
        //查找当前的根节点
        int i; i=0;
        LNode *p;p=L->next;
        while(p)
        {
            i++;
            if(i==mid)
                break;
            p=p->next;
        }
        b->data = p->data;
        b->lchild = tranTobtree(L,ln);
        b->rchild = tranTobtree(p->next,rn);
        return b;
    }
}

2.2.5 链表计数

​ 1.设键盘输入n个英语单词,输入格式为n, w1, w2,…,wn,其中n表示随后输入英语单词个数,试编一程序,建立一个单向链表,实现:

(1)如果单词重复出现,则只在链表上保留一个。

(2)除满足(1)的要求外。链表结点还应有一个计数域,记录该单词重复出现的次数,然后输出出现次数最多的前k(k<=n)个单词。

typedef  struct  node
{
    int  freg;//频度域,记单词出现的次数。
    char  word[maxsize];//maxsize是单词中可能含有的最多字母个数。
    struct  node  *next;
}node, *LinkedList;
(1)
LinkedList creat()
//建立有n(n>0)个单词的单向链表,若单词重复出现,则只在链表中保留一个。
{
    LinkedList la;
    la=(LinkedList)mallocsizeof(node));//申请头结点。
    la->next=null;       //链表初始化。
 	for(i=1;i<=n;i++) //建立n个结点的链表
    {
        scanf(“%s”,a);  //a是与链表中结点数据域同等长度的字符数组。
        p=la->next;pre=p; //p是工作指针,pre是前驱指针。
    	while(p!=null)
      		ifstrcmp(p->data,a)==0)
        		{p->freg++;break;} //单词重复出现,频度增1。
     		else {pre=p;p=p->next;}     //指针后移。
    	if(p==null)     //该单词没出现过,应插入。
     	{
            p=(LinkedList)mallocsizeof(node));
       		strcopy(p->data,a);p->freg=1;p->next=null;pre->next=p;
    	}     //将新结点插入到链表最后。
    }
	return(la);
}

(2)
void CreatOut()
//建立有n个单词的单向链表,重复单词只在链表中保留一个,最后输出频度最高的k个单词。
{
    LinkedList la;
    la=(LinkedList)mallocsizeof(node));//申请头结点。
 	la->next=null;       //链表初始化。
 	for(i=1;i<=n;i++) //建立n个结点的链表
   	{
        scanf(“%s”,a);  //a是与链表中结点数据域同等长度的字符数组。
    	p=la->next;pre=p; //p是工作指针,pre是前驱指针。
    	while(p!=null)
      		ifstrcmp(p->data,a)==0)
 			{  p->freg++;        //单词重复出现,频度增1。
                pre->next=p->next; //先将p结点从链表上摘下,再按频度域值插入到合适位置
                pre=la; q=la->next;
             	while(q->freg>p->freg)
                    (pre=q; q=q->next; )
  				pre->next=p; p->next=q; //将p结点插入到合适位置
 			}
      		else {pre=p;p=p->next;}     //指针后移。
   		if(p==null)      //该单词没出现过,应插入到链表最后。
        {
            p=(LinkedList)mallocsizeof(node));
            strcopy(p->data,a);p->freg=1;p->next=null;pre->next=p;
    	}//if 新结点插入。
 	 }//结束for循环建表。
	int k,i=0;
	scanf(“输入要输出单词的个数%d”,&k);
	p=la->next;
	while (p && i<k) ∥输出频度最高的k个单词
  	{
        printf(“第%3d个单词%s出现%3d次\n”,++i,p->data,p->freg);
   		p=p->next;
  	}
	if (!p)
   		printf(“给出的%d值太大\n”,k);
}

​ 2.删除链表中绝对值相等的点,|data|<n

​ 类似哈希表的用法,以空间换时间。

void deleteSame(LNode *L)
{
    int i,S[n];
    for(i=0;i<=n;i++) S[i] = 0;
    SNode *p,*pre,*t;
    p = L->next;pre = L;
    while(p)
    {
        if(S[p->data]==0)
        {
         	S[p->data] = 1;
            pre = p;
            p = p->next;
        }
        else
        {
            t=p;p=p->next;
            pre->next = p->next;
            free(t);
        }
    }
}

​ 3.已知长度为n的线性表A采用顺序存储结构,请写一时间复杂度为0(n)、空间复杂度为0(1)的算法,该算法删除线性表中所有值为item的数据元素

void  Delete(ElemType A[ ],int  n)
//A是有n个元素的一维数组,本算法删除A中所有值为item的元素。
{
	i=1;j=n;设置数组低、高端指针 下标
 	while(i<j)
   {
   		while(i<j && A[i]!=item)i++;    //若值不为item,左移指针。
    	if(i<j)
    		while(i<j && A[j]==item)j--;//若右端元素值为item,指针左移
    	if(i<j)A[i++]=A[j--];
   }

​ 4.建立双向链表实现二进制数的加减,注意溢出和进位。

​ 双向链表,进位则增加前面的节点