薛磊 Job Seeker

数据结构——1.线性表

2019-03-28

1.顺序表

例1-1

​  已知长度为N的线性表A采用顺序存储结构。设计一个时间复杂度为O(n)、空间复杂度为O(1)的算法,该算法删除线性表中所有值为x的数据元素。

解:以下两种方法都不满足要求:

  • 如果没删除一个值为x的元素都要进行移动,其时间复杂度为O(n^2),空间复杂度为O(1);
  • 如果借助一个新的顺序表,存放将A中所有不为x的元素,其时间复杂度为O(n),空间复杂度为O(n)。
//解法一:设删除A中所有值等于x元素后的顺序表位A1,,显然A1包含在A中,为此A1重用A的空间
public void delonde1(SqList *&A,ElemType x){
    int k=0,i;	//k记录值不等于x的元素个数
    for(i=0;i<arr.length;i++)
        if(A->data[i]!=x){	//若当前元素不为x,将其插入A中
            A->data[k] = A->data[i];
            k++;	//不等于x的元素增1
        }
    A.length=k;	//顺序表A的长度等于k
}
//解法二:用k记录顺序表A中等于x的元素个数,一边扫描A以便统计k值
public void delonde1(SqList *&A,ElemType x){
    int k=0,i=0;	//k记录值不等于x的元素个数
    while(i<A->length){
        if(A->data[i]==x) k++;	//当元素值为x时k增1
        else A->data[i-k] = A->data[i];	//当前元素不为x时将其前移k个位置
        i++;
    }
    A.length=k;	//顺序表A的长度等于k
}

例1-2

​  设顺序表L有10个正整数。设计一个算法,以第一个元素为分界线,将所有小于等于它的元素移到该元素的前面,将所有大于它的元素移到该元素后面。

//解法一:i从前向后找>pivot的元素,j从后向前找<=pivot的元素,两者交换
void move1(int[] arr) {
	int i=0,j=arr.length-1,temp;
	int pivot = arr[0];
	while(i<j) {
		//从后向前扫描,再找一个<=pivot的元素
		while(i<j&&arr[j]>pivot)
			j--;
		while(i<j&&arr[i]<=pivot)
			i++;
		if(i<j) {
			temp = arr[i];
			arr[i] = arr[j];
			arr[j] = temp;
		}
	}
	temp = arr[0];
	arr[0]=arr[j];
	arr[j] = temp;
}
//解法二:j从后向前找下雨等于pivot的元素:前移;i从前向后找大于pivot的元素:后移。性能比方法一更好
void move2(int[] arr) {
    int i=0,j=arr.length-1;
    int pivot = arr[0];
    while(i<j) {
        //从后向前扫描,再找一个<=pivot的元素
        while(i<j&&arr[j]>pivot)
            j--;
        arr[i]=arr[j];
        while(i<j&&arr[i]<=pivot)
            i++;
        arr[j]=arr[i];
    }
    arr[j] = pivot;
}

2.单链表

例2-3

​  设计一个算法,删除一个单链表L中元素值最大的节点。

//定义两个指针pre和p同步向后移动,指针maxpre记录最大值的前驱,maxp记录最大值位置
void delmaxnode(LinkList *&L){
    LinkList *p=L->next,*pre=L,*maxpre=pre,*maxp=p;
    while(p!=NULL){
        if(maxp->data < p->data){	//若找到一个更大的结点
            maxp = p;
            maxpre = pre;
        }
        pre = p;
        p = p->next;
    }
    maxpre->next = maxxp->next;	//删除*maxp节点
    free(maxp);	//释放*maxp节点
}

例2-4

​  有一个带头节点的单链表L,设计一个算法使其元素递增有序排列。

void sort(LinkList *&L){
    LinkList *p,*pre,*p;
    p=L->next->next;	//P指向L的第2个数据节点
    L->next->next=NULL;	//构造只含一个数据节点的有序表
    while(p!=NULL){
        q=p->next;	//q保存*p节点后继节点的指针
        pre=L;	//从有序表开头进行比较,pre指向插入*p的前驱节点
        while(pre->next!=NULL && pre->next->data < p->data)
            pre=pre->next;	//在有序表中找插入*p的前驱节点*pre
        p->next = pre->next;
        pre->next = p;
        p=q;	//扫描单链表余下的结点
    }
}

例2-5

​  假设有一个带头结点的单链表L={a1,a2,…,an}。设计一个算法将所有节点逆置。

//头插法
void Reverse(LinkList *&L){
    LinkList *p = L->next,*p;
    L->next = NULL;
    while(p!=NULL){
        q = p->next;
        p->next = L->next;
        L->next = p;
        p = q;
    }
}

例2-6

​  假设有一个带头结点的单链表L={a1,b2,a2,v2,…,an,bn}。设计一个算法将其拆分成两个带头结点的单链表L1和L2: L1={a1,a2,…},L2={b1,b2,…},要求L1使用L的头结点

void split(LinkList *&L,LinkList *&L1,LinkList *&L2){
    LinkeList *p = L->next,*p,*r1;	//p指向第1个数据节点
    L1 = L;	//L1利用原来L的头结点
    r1 = L1;	//r1四种指向L1的尾节点
    L2 = LinkList *)malloc(sizeof(LinkList));	//创建L2的头结点
    L2->next = NULL;	//置L2的指针域为NULL
    while(p!=NULL){
        r1->next = p;	//采用尾插法将*p插入L1中
        r1 = p;		
        p = p->next;	//p移向后一个节点
        q = p->next;	//用q保存*p的后继节点
        p->next = L2->next;	//采用头插法将*p插入L2中
        L2->next = p;	
        p=q;	//p重新指向 a i+1 的结点
    }
    r1->next = NULL;	//尾节点next置空
}

练习:荷兰国旗问题

​  设有一个条块序列,每个条块为红(0)、白(1)、兰(2)三种颜色中的一种。假设该序列采用顺序表/单链表存储,设计一个时间复杂度为O(n)的算法,使得这些条块按红、白、兰的顺序排好,即排成荷兰国旗图案。

解1:用0~i表示0元素区间,k~n-1表示2元素区间,中间部分为1元素区间,用j从头开始扫描顺序表L中部的所有元素。

每一次循环:

  • j指向元素1:说明它属于中部,保持不动,j++。
  • j指向元素0:说明它属于前部,i增1(扩大0元素区间),将i、j位置的元素交换,j++。
  • j指向元素2:说明它属于后部,k减1(扩大2元素区间),将j、k位置的元素交换,此时j位置的元素可能还要交换到前部,所以j不前进。
void move1(SqList *&L){
    int i=-1,j=0,k=L->length;
    while(j<k){
        if(L->data[j]==0){
            i++;
            swap(L->data[i],L->data[j]);
            j++
        }else if(L->data[j]==2){
            k--;
            swap(L->data[k],L->data[j]);
        }else j++;
    }
}

解2:用p指针扫描节点,根据p->data值将该节点插入到3个单链表L、L1和L2(L1和L2不带头节点的)中,最后将它们连接起来。

void move2(LinkList *&L){
    LinkList *L1,*L2,*r1,*r2,*p;
    L1=NULL;
    L2=NULL;
    p=L->next;
    r=L;
    while(p!=NULL){
        if(p->data==0){		//建立L带头节点的单链表
            r->next=p;r=p;
        }else if(p->data==1){	//建立L1不带头节点的单链表
            if(L1==NULL) {L1=p;r1=p;}
            else {r1->next=p;r1=p;}
        }else{
            if(L2==NULL) {L2=p;r2=p;}
            else {r2->next=p;r2=p;}
        }
        p=p->next;
    }
    r->next=r1->next=r2->next=NULL;
    r->next=L1;
    r1->next=L2;
}

3.双链表

例3-1

​  有一个带头节点的双链表L,设计一个算法将其所有元素逆置,即第1个元素变成最后一个元素,第2个元素变成倒数第2个元素,…,最后一个元素编程第1个元素

void Reverse(DLinkList *&L){
    DLinkList *p=L->next,*p;	//p指向
    L->next=NULL;	//构造只有头节点的双链表L
    while(p!=NULL){	//扫描L的数据节点
        q=p->next;	//用q保存其后继节点
        p->next=L->next;	//采用头插法将*p节点插入
        if(L->next!=NULL)	//修改其前驱指针
            L-<next->prior=p;
        L->next=p;
        p->prior=L;
    }
}

4.循环链表

例4-1

​  设计判断带头节点的循环双链表L(含两个以上的节点)是否对称相等的算法。

解:

  • p从左向右扫描L,q从右向左扫描L
  • 若对应数据节点的data域不相等,则退出小循环
  • 否则继续比较,直到p与q相等或p的下一个节点为*q为止。
  • 奇数情况和偶数情况有些不同
int Equal(DLinkList *L){
    int same = 1;
    DLinkList *p=L->next;	//p指向第一个数据节点
    DLinkList *q=L->prior;	//q指向最后数据节点
    while(same==1){
        if(p->data!=q->data)
            same=0;
        else{
            if(p==q || p==p->prior) break;
            q=q->prior;	//q前移
            p=p->next;	//p后移
        }
    }
    return same;
}

5.有序表

例5-1

​  假设有两个有序表LA和LB。设计一个算法,将它们合并陈一个有序表LC。(归并算法

//本算法的时间复杂度为O(m+n),空间复杂度为O(m+n)
//采用顺序表存放有序表
void UnionList(SqList *LA,SqList *LB,SqlList *&LC){
    int i=0,j=0,k=0;	//i、j分别为LA、LB的下标,k为LC中元素个数
    LC=(SqList *)malloc(sizeof(SqList));	//建立有序顺序表LC
    while(i<LA->length && j<LB->length){
        if(LA->data[i] < LB->data[j])
            LC->data[k++] = LA->data[i++];
        else LC->data[k++] = LB->data[j++];
    }
    while(i<LA->length)
        LC->data[k++] = LA->data[i++];
    while(j<LB->length){
        LC->data[k++] = LB->data[j++];
    }
    LC->length = k;
}

例5-2

​  一个长度为L的升序序列S,处在L/2向上取整的位置的数成为S的中位数。找出两个等长序列的所有元素的中位数。

解:首先可以采用归并算法,将两个序列合并,然后去中位数即可。

​  因为只需要找n/2,所以可以定义两个下标分别指向两个数组,要来比较。

//算法的时间复杂度为O(n),空间复杂度为O(1)
//n是等长序列的长度
int M_Search(int A[],int B[],int n){
    int i,j,k;
    i=j=k=0;
    while(i<n && j<n){
        k++;
        if(A[i]<B[j]){
            if(k==n) return A[i];
            i++;
        }else{
            if(k==n) return B[j];
            j++;
        }
    }
}

例5-3

​  假设一个有序表采用顺序表存储,设计一个高效算法删除重复的元素。

void deldupnode1(SqList *&L){
    int k=1,i=0;
    while(i<L->length-1){
        if(L->data[i]!=L->data[i+1])
            L->data[k++] = L->data[i++];
    }
    L->length=k;
}

例5-4

​  假设两个递增有序表采用单链表ha和hc存储。设计一个高效算法求它们的公共元素,将结果存放在单链表hc中。

void InterSect(LinkList *ha,LinkList *hb,LinkList *&hc){
    LinkList *pa=ha->next,*pb=pb->next,*s,*r;
    hc=(LinkeNode *)malloc(sizeof(LinkeNode));
    r=hc;
    while(pa!=NULL&&pb!=NULL){
        if(pa->data < pb->data) pa=pa->next;
        if(pa->data > pb->data) pb=pb->next;
        if(pa->data == pb->data){
            s==(LinkeNode *)malloc(sizeof(LinkeNode));
            s->data = pa->data;
            r->next = s; r = s;
            pa = pa->data; pb = pb->next;
        }
    }
    r->next=NULL;
}

下一篇 Learning English

Comments

Content