2021-02-10

C++语言实现单链表

单链表的优缺点
单链表的优点:

  1. 进行插入与删除操作只需要移动一个指针,相比顺序表更加方便快捷。
  2. 没有空间限制,可以自由伸缩(只要内存充足),可以按需随时分配内存。

单链表的缺点:

  1. 需要占用额外的存储空间用来存放指针。
  2. 访问元素需要指针从头开始遍历,访问速度较慢。

算法实现:

头文件:

#ifndef _LINK_LIST_H_
#define _LINK_LIST_H_

#define ELEMENT_TYPE int

typedef struct ListNode{
    
    
	ELEMENT_TYPE ele;
	struct ListNode* next;
}Node;
typedef Node* LinkList_t;

//向单链表头部插入元素[时间复杂的o(1)]:
bool push_front(LinkList_t& head, const ELEMENT_TYPE& e);
//擦除单链表头部的元素[时间复杂的o(1)]:
bool pop_front(LinkList_t& head);
//向单链表尾部插入元素[时间复杂度o(n)]:
bool push_back(LinkList_t& head, const ELEMENT_TYPE& e);
//擦除单链表尾部的元素[时间复杂的o(n)]:
bool pop_back(LinkList_t& head);
//向指定位置插入元素[时间复杂度o(n)]:
bool insert(LinkList_t& head, const unsigned& pos, const ELEMENT_TYPE& e);
//擦除指定位置的元素[时间复杂度o(n)]:
bool erase(LinkList_t& head, const unsigned& pos);
//在单链表中查找指定元素并返回其位置,未找到则返回-1[时间复杂度o(n)]:
int find(const LinkList_t& head, const ELEMENT_TYPE& e);
//获取指定位置节点的地址[时间复杂度o(n)]:
const Node* getAddr(const LinkList_t& head, const unsigned& pos);
//显示单链表中的元素:
void display(const LinkList_t& head);

#endif // !_LINK_LIST_H_

CPP文件:

#include "LinkList.h"
#include<iostream>
using namespace std;

bool push_front(LinkList_t& head, const ELEMENT_TYPE& e)
{
    
    
    //创建一个临时指针保存头指针中存放的地址,方便新插入元素
    //的next指针指向该地址而成为链表中的第一个元素。
    Node* const tmp = head;  //tmp作用是临时存放原首节点的地址。
    head = new Node;  
    if (!head) return false;  //防止内存分配出错。
    head->ele = e; 
    head->next = tmp;
    return true;
}

bool pop_front(LinkList_t& head)
{
    
       //链表为空,无法执行擦除操作。
    if (!head) return false;
    //链表不为空时,释放头部节点的内存:
    Node* tmp = head;
    head = head->next;
    delete tmp;
    return true;
}

bool push_back(LinkList_t& head, const ELEMENT_TYPE& e)
{
    
       //当链表为空:
    if (!head) {
    
    
        head = new Node;
        if (!head) return false;  //防止内存分配出错。
        head->ele = e;
        head->next = nullptr;
    }
    else {
    
      //当链表不为空:
        Node* tmp = head;
        //当tmp指向的不是尾元素时,tmp循环指向下一个元素
        while (tmp->next) {
    
    
            tmp = tmp->next;
        }//当tmp指向了尾元素,让尾元素的指针域指向一个新节点
        tmp->next = new Node;
        if (!tmp) return false;  //防止内存分配出错。
        tmp->next->ele = e;
        tmp->next->next = nullptr;
    }
    return true;
}

bool pop_back(LinkList_t& head)
{
    
       //链表为空,无法执行擦除操作。
    if (!head) return false;
    //链表不为空时,释放尾部节点的内存,有两种情况:
    Node* tmp = head;
    //1.若链表中只有一个元素:
    if (!tmp->next) {
    
    
        delete tmp;
        head = nullptr;
        return true;
    }
    //2.若链表中超过一个元素:循环迭代,直到tmp指向倒数第二个元素。
    while (tmp->next->next) {
    
    
        tmp = tmp->next;
    }//当tmp指向了倒数第二个元素,利用倒数第二元素的指针
    //域释放尾元素内存。
    delete tmp->next;
    //让倒数第二元素的指针域为null,变成尾元素。
    tmp->next = nullptr;
    return true;
}

bool insert(LinkList_t& head, const unsigned& pos, const ELEMENT_TYPE& e)
{
    
    
    if (!pos) return push_front(head, e);  //若指定插入位置为0。
    if (!head) return false;  //若head为空链表且指定插入位置不为0。
    //两个临时指针,tmp1指向插入位置之前的节点,tmp2指向插入位置处的原有节点。
    Node* tmp1 = head, *tmp2 = nullptr;
    //tmp1初始指向0号节点,i初始值为0,每次i加1,tmp1指针后移一位,这样i的值
    //刚好等于tmp1指向的位置。循环移动tmp1指针pos-1次,使tmp1指向第pos-1个节点。
    for (unsigned i = 0; i != pos - 1; ++i) {
    
    
        //若tmp1还未到达pos-1位置处,其指针域就为NULL,说明插入
        //位置pos大于链表长度,无法执行插入操作!
        if (!tmp1->next) return false;
        //否则,tmp1指针后移。
        tmp1 = tmp1->next;
    }
    //循环结束时,tmp1指向了pos-1位置,此时让tmp2指向pos位置。
    tmp2 = tmp1->next;
    //让pos-1位置的节点的指针域指向一个新节点,让新节点的指针域指向原链表pos位
    //置处的节点:
    tmp1->next = new Node;
    if (!tmp1) return false;
    tmp1->next->ele = e;
    tmp1->next->next = tmp2;
    return true;
}

bool erase(LinkList_t& head, const unsigned& pos)
{
    
    
    if (!head) return 0;  //若head为空链表。
    if (!pos) return pop_front(head);  //若指定擦除位置为0。
    //两个临时指针,tmp1指向删除位置之前的节点,tmp2指向需要删除的节点。
    Node* tmp1 = head, * tmp2 = nullptr;
    //tmp1初始指向0号节点,i初始值为0,每次i加1,tmp1指针后移一位,这样i的值
    //刚好等于tmp1指向的位置。循环移动tmp1指针pos-1次,使tmp1指向第pos-1个节点。
    for (unsigned i = 0; i != pos - 1; ++i) {
    
    
        //若tmp1还未到达pos-1位置处,其指针域就为NULL,说明指定擦除位置pos大于
        //链表长度,无法执行擦除操作!
        if (!tmp1->next) return false;
        //否则,tmp1指针后移。
        tmp1 = tmp1->next;
    }
    //循环结束时,tmp1指向了pos-1位置。注意,此时与insert不同的是,链表尾节点
    //的后一个位置能插入节点却无法删除节点,因此先判断此时pos-1位置是否是最后
    //一个节点,若是,说明pos位置无法执行擦除操作。
    if (!tmp1->next) return false;
    //否则,让tmp2指向pos位置。
    tmp2 = tmp1->next;
    //让pos-1位置的节点的指针域指向pos后面那个节点,并释放pos处的节点内存。
    tmp1->next = tmp2->next;
    delete tmp2;
    return true;
}

int find(const LinkList_t& head, const ELEMENT_TYPE& e)
{
    
    
    const Node* tmp = head;
    int pos = 0;
    while (tmp) {
    
    
        //让tmp指针遍历链表,若tmp指向了一个节点(不为null)且该节点的数据域与e相等,则
        //返回此时tmp指针所指节点的位置pos。
        if (tmp->ele == e) return pos;
        tmp = tmp->next;
        ++pos;
    }
    return -1;  //tmp指向了null(链表尾)也未找到指定元素。
}

const Node* getAddr(const LinkList_t& head, const unsigned& pos)
{
    
    
    const Node* tmp = head;
    unsigned index = 0;  //index指示tmp指针目前指向的节点位置。
    //让tmp指针对链表从头遍历到尾(即tmp为null),若遍历到了指定位置pos,则
    //跳出循环。
    while (tmp) {
    
    
        if (index == pos) break;
        tmp = tmp->next;
        ++index;
    }
    return tmp;  //若遍历完整个链表未找到pos位置,则tmp必为null。
}

void display(const LinkList_t& head)
{
    
    
    const Node* tmp = head;
    int count = 0;
    while (tmp) {
    
    
        cout << tmp->ele << "  ";
        tmp = tmp->next;
        ++count;
    }
    cout << "\n元素个数:" << count << endl;
}

猜你喜欢

转载自blog.csdn.net/weixin_48343353/article/details/113785853