数据结构学习笔记(四)单链表的算法操作

默认带头结点

1.尾插法建单链表

假设有n个元素已经存储在数组a中,用尾插法建立链表C:

void CreatelistR (LNode *&C, int a[], int n) { //要改变的变量用引用型。
    LNode *s, *r;
    //s用来指向新申请的结点,r始终指向C的终端结点。
    int i;
    C = (LNode *)malloc(sizeof(LNode)); 
    //申请C的头结点空间。
    C->next = NULL;
    r = C;
    //r指向头结点,因为此时头结点就是终端结点。
    for(i=1;i<=n;i++){  //循环申请n个结点来接受数组a中的元素。
        s = (LNode *)malloc(sizeof(LNode)); 
        //s指向新申请的结点。

        s->data = a[i]; 
        //用新申请的结点来接受a中的一个元素。

        r->next = s; 
        //用r来接纳新结点。

        r = r->next;
        //r指向终端结,点以便于接纳下一个到来的结点。

    }
    r->next=NULL; 
    //数组a中所有的元素都已经装入链表C中,C的终端结点的指//针域置为NULL,C建立完成。
}

2.头插法

在头插法算法中不断的将新结点插入链表的前端,因此新建立的链表中元素的次序和数组a中的元素的次序是相反的。
代码如下:

void CreatelistF (LNode *&C,int a[],int n) { 
    LNode *s;
    int i; 
    C = (LNode *)malloc(sizeof(LNode));
    C->next = NULL;
    for(i = 1; i< = n; i++){ 
        s = (LNode*)malloc(sizeof(LNode));
        s->data = a[i]; 

        /*下边两句是头插法的关键步骤。*/ 
        s->next = C->next;
        //s所指新结点的指针域next指向C中的开始结点。

        C->next = s;
        //头结点的指针域next指向s结点,使得s成为了新的开始结点。
    }
}

3.递增归并单链表:

假设A和B是两个单链表(带表头结点),其中元素递增有序。设计一个算法将A和B归并成一个按元素递增有序的链表C,C由A和B中的结点组成。

分析:
已知A,B中的元素递增有序,可以从A,B中挑出最小的元素插入C的尾部,这样当A,B中所有元素都插入C中的时候,C一定是递增有序的。由于A,B是递增的,所以A中的最小元素是其开始结点中的元素,B也一样。只需从A,B的开始结点中选出一个较小的来插入C的尾部即可。这里还需注意,A与B中的元素有可能一个已经全部被插入到C中,另一个还没有插完,比如A中所有元素已经全部被插入到C中而B还没有插完,这说明B中所有元素都大于C中元素,因此只要将B链接到C的尾部即可,如果A没有插完则用类似的方法来解决。

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的头结点。

    C->next = NULL;
    free(B);
    //B的头结点已无用,则释放掉。

    r = C;
    //r指向C,因为此时头结点也是终端结点。 

    while(p! = NULL&&q! = NULL){ //当p与q都不空时选取p与q所指结点中的较小者插入C的尾部。

        /*以下的ifelse语句中,r始终指向当前链表的终端节点,作为接纳新结 点的一个媒介,通过它新结点被链接入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;

    /*以下两个if语句将还有剩余结点的链表链接在C的尾部*/ 
    if(p! = NULL) 
        r->next = p;
    if(q! = NULL) 
        r->next = q;
}

4.递减归并的单链表算法:

A和B是两个单链表(带表头结点),其中元素递增有序。设计一个算法将A和B归并成一个按元素递减有序的链表C,C由A和B中的结点组成。
将插入过程改成头插法即可解决。代码如下,这里不需要r追踪C的终端结点,用s来接受新的结点插入链表C的前端。

void merge (LNode *&A, LNode *&B, LNode *&C){ 
    LNode *p = A->next;
    LNode *q = B->next;
    LNode *s;
    C = A;
    C->next = NULL;
    free(B);
    while(p! = NULL && q! = NULL) {
    /*下边这个if else语句体现了链表的头插法*/ 
        if(p->data <= q->data){ 
            s = p;
            p = p->next;
            s->next = C->next;
            C->next = s;
        } else { 
            s = q;
            q = q->next;
            s->next = C->next;
            C->next = s;
        }
    }

    /*下边这两个循是和求递增归并序列不同的地方,必须将剩余元素逐个插入C的头 部才能得到最终的递减序列。*/
    while(p != NULL) { 
        s = p;
        p = p->next;
        s->next = C->next;
        C->next = s;
    } 
    while(q! = NULL){ 
        s = q;
        q = q->next;
        s->next = C->next;
        C->next = s;
    }
}

5.插入节点的关键步骤

q = p->next; 
p->next = p->next->next;

clipboard.png

注意:以上插入操作语句不可以颠倒顺序写成p->next=s; s->next=p->next;,因为第一句p->next=s;虽然将s链接在p之后,但是同时也丢失了p直接后继结点的地址(p->next指针原本所存储的p直接后继结点的地址在没有被转存到其他地方的情况下被s所覆盖,而正确的写法中,p->next中的值在被覆盖前被转存在了s->next中,因而p后继结点的地址依然可以找到),这样链表断成了两截,没有满足将s插入链表的要求。

6.删除节点的关键步骤

要将单链表的第i个结点删去,必须先在单链表中找到第i-1个结点,再删除其后继结点。如下图所示,若要删除结点b,为了实现这一逻辑关系的变化,仅需要修改结点a中的指针域。假设p为指向a的指针。则只需将p的指针域next指向原来p的下一个结点的下一个结点即可。即:

q = p->next; 
p->next = p->next->next;

clipboard.png

删除节点范例

查找链表C(带头结点)中是否存在一个值为x的结点,存在就删除之并返回1,否则返回0。

分析:
对于本题需要解决两个问题,一是要找到值为x的结点;二是将找到的结点删除。。为了实现查找,我们定义一个结点指针变量p,让他沿着链表一直走到表尾,每来到一个新结点就检测其值是否为x,是则证明找到,不是则继续检测下一个结点。

int SearchAndDelete (LNode *&C, int x){ 
    LNode*p,*q;
    p = C;
    /*查找部分开始*/ 
    while(p->next! = NULL){ 
        if(p->next->data == x) 
            break;
        p = p->next;
    } 
    /*查找部分结束*/ 

    if(p->next == NULL) 
        return 0;
    else{ 
        /*删除部分开始*/
        q = p->next;
        p->next = p->next->next;
        free(q);
        /*删除部分结束*/
    return 1;
    }
}

说明:以上程序中之所以要使p指向所要删除结点的前驱结点而不是直接指向所要删除结点本身,是因为要删除一个结点必须知道其前驱结点的位置.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值