单链表的基本操作

   单链表是一个非常常用的数据结构,不得不说,它非常简单,也不得不说,还是有很多细节需要注意的。

   一般说来,单链表的单个结点都会被定义为一个struct,结构中则主要包含两个成员,用于存储结点键值的data,和用于指向链表中下一个结点的指针next。为了便于操作,一个链表通常会包含一个头结点,头结点中存储的不是用户数据,而是一些另外的信息,如链表中结点的个数等,头结点的next指针域指向链表中的第一个有效结点(首结点)。头结点的维护,对于某些信息的获取,及结点的插入和删除都带来了极大的便利。链表头结点指针head是每个链表中都会有的,大多数链表也会维护一个尾结点指针tail,其目的当然也是方便操作。

基本操作

   单链表的基本操作包括创建链表、插入结点、删除结点、获取链表长度(不包括头结点)、逆置链表、链表排序、清空链表、销毁链表等。

   创建链表的主要目的是创建头结点,并使得头指针head指向头结点,同时将头结点数据域中存储的链表长度初始化为0。

   插入节点都在链表的头部进行,因为这样操作很方便,当然,如果维护了尾指针tail,也可以从尾部插入结点。重点是要方便地确定新结点的插入位置。

   删除结点时,通过参数idx来确定被删除结点的位置,有效结点的索引从0开始编号。

   直接返回头结点中数据域的值就可以获取链表长度

   逆置链表时,需要借助三个指针p1p2p3,依次指向原链表中连续的三个结点,其中p1和p2的指向关系需要修改,p3主要用于保存后续结点的位置。如下图所示,p1p2p3位置确定后,让p2->next指向p1,然后依次将p1p2p3后移(在图中的位置),重新修改p1p2的指向关系,当p2为空时,表明链表已经遍历完毕,相应的位置也已修改完毕,此时修改链表头即可。整个过程只需遍历一次链表即可。
逆置链表

   链表排序相对来说是一个比较复杂的过程,首先考虑到对于单链表,访问某一结点的后续结点是比较容易的,但是要访问某一结点的前序结点,却似乎只能通过遍历的方式来完成;此外,在结点需要重排序时,如果直接通过修改指针来改变整个结点的位置,一方面操作复杂,需要考虑各种边界条件,另一方面指针操作稍有不当就会发生各种错误。综合考虑单链表的上述特征,觉得冒泡排序似乎是比较合适的。

   清空链表的操作,只是将链表中的有效结点全部删除,只保留头结点,也就是,链表还存在,只是它是空的。删除每个结点的同时,要注意释放掉结点所占用的内存空间。

   销毁链表操作相对于清空链表的操作更为彻底,除了要删除链表中的有效结点,还要将头结点也删除。也就是最后,这个链表已经不存在了。


代码实现

   基于上述分析,最终代码实现如下:

LinkedList.h

#ifndef _LIST_H_
#define _LIST_H_

#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <iomanip>
using namespace std;

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

node * createList();
int getLength(node * head);
bool insertNode(node * head, int e);
bool deleteNode(node * head, int idx);
void reverseList(node * head);
void sortList(node * head);
void emptyList(node * head);
void destroyList(node * head);
void printList(node * head);
bool getMidNode(node * head, int &mid);

#endif

LinkedList.cpp

#include "LinkedList.h"

node* createList()
{
    node *head;
    head = (node *)malloc(sizeof(node));
    if(!head){
        return NULL;
    }
    head->data = 0;
    head->next = NULL;
    return head;
}

int getLength(node * head)
{
    if(head == NULL){
        return -1;
    }
    return head->data;
}

bool insertNode(node * head, int e)
{
    node * newNode;
    if(!head){
        return false;
    }

    newNode = (node *)malloc(sizeof(node));
    if(!newNode){
        return false;
    }
    newNode->data = e;
    newNode->next = head->next;
    head->next = newNode;
    head->data ++;
    return true;
}

bool deleteNode(node * head, int idx)
{
    int i;
    node * temp,*p;
    int length = getLength(head);
    if(!head || idx>=length){
        return false;
    }
    p = head;
    temp = p->next;
    for(i=0;i<length;i++){
        if(i == idx){
            p->next = temp->next;
            temp->next = NULL;
            free(temp);
            head->data --;
            break;
        }
        p = temp;
        temp = p->next;
    }

    return true;
}

void reverseList(node * head)
{
    node * p1,*p2,*p3;
    if(!head)
    {
        return;
    }
    if(getLength(head) <= 1){
        return;
    }
    p1 = head->next;
    p2 = p1->next;
    p1->next = NULL;
    while(p2){
        p3 = p2->next;
        p2->next = p1;
        p1 = p2;
        p2 = p3;
    }

    head->next = p1;
}

void sortList(node * head)
{
    int i,j,e;
    int length;
    node * p1,* p2;
    if(!head){
        return;
    }
    length = getLength(head);
    if(length<=1){
        return ;
    }
    for(i=1;i<length;i++){
        p1 = head->next;
        p2 = p1->next;
        for(j=length-i;j>=1;j--){
            if(p1->data > p2->data){
                e = p1->data;
                p1->data = p2->data;
                p2->data = e;
            }
            p1 = p2;
            p2 = p1->next;
        }
    }
}

void emptyList(node * head)
{
    node * temp;
    if(!head){
        return ;
    }
    if(getLength(head) == 0){
        return ;
    }
    temp = head->next;
    while(temp){
        head->next = temp->next;
        temp->next = NULL;
        free(temp);
        temp = head->next;
    }
    head->data = 0;
}

void destroyList(node * head)
{
    if(!head){
        return;
    }
    emptyList(head);
    free(head);
}

void printList(node* head)
{
    node *temp;
    if (!head){
        return;
    }
    temp = head->next;
    while(temp){
        cout << left << setw(5) << temp->data ;
        temp = temp->next;
    }
    cout << endl;
}

bool getMidNode(node * head, int & mid)
{
    node * p,*q;
    if(!head){
        return false;
    }
    if(getLength(head)==0){
        return false;
    }
    p = q = head;
    while(q){
        q = q->next;
        if(!q){
            break;
        }
        p = p->next;
        q = q->next;
    }
    mid = p->data;
    return true;
}

test.cpp

#include "LinkedList.h"

int main()
{
    int mid;
    node * head;
    int rawData[] = {1,3, 2, 5, 7, 9, 6, 0};
    head = createList();
    if(head == NULL){
        cout << "can't create list!" << endl;
        return -1;
    }

    for(int i=0;i<sizeof(rawData)/sizeof(int);i++){
        insertNode(head,rawData[i]);
    }
    printList(head);
    cout << "-----" << endl;
    if(getMidNode(head,mid)){
        cout << "Mid Node : " << mid << endl;
    }
    cout << "-----" << endl;

    reverseList(head);
    printList(head);

    insertNode(head,10);
    printList(head);
    cout << "-----" << endl;
    if(getMidNode(head,mid)){
        cout << "Mid Node : " << mid << endl;
    }
    cout << "-----" << endl;

    deleteNode(head,0);
    printList(head);

    sortList(head);
    printList(head);

    destroyList(head);

    return 0;
}

运行结果
这里写图片描述

   本来是打算封装一个类来实现的,但是在实现的过程中,因为涉及到指针操作以及实例的析构,遇到了各种问题。果然,一旦涉及到内存的分配和释放,就得格外留心,暂时先这样吧,链表类的实现,等稍后问题都解决了,再补上吧。


餐后甜品

   如果给你一个单链表,但是不告诉你链表长度是多少(你也不能直接获取到这个长度),你怎么能够只遍历一次,就取到中间结点的值呢?

   问题的解法很巧妙(起码我这么认为),就是利用两个结点指针pq,初始时刻,它们都指向头结点,然后开始遍历,p结点每次向后遍历的跨度是一个结点,而q结点每次向后遍历的跨度是两个结点,那么当q结点为空时,p结点所对应的就是链表的中间结点了。对应的代码实现,就是getMidNode()函数了。


小贴士
   还是那句话,操作指针时要格外留心。一旦决定要使用某一个指针,一定要确保该指针被初始化了,即使该指针只是作为一个参数传入某一个函数也不能例外;当试图通过指针获取某些内容时,一定要先确保该指针不是空指针,否则编译不会出错,但是运行时就会出现很大的错误了。
   一定要注意函数参数的传递方式,当你要试图通过参数获取某一个值时,采用引用传递是一个不错的选择;地址传递也是可行的,只是操作要格外小心;值传递就不要想了,因为这样你是不可能通过这个参数获取到任何东西的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值