参考:https://blog.youkuaiyun.com/LKJgdut/article/details/105283861
1、链表
链表的添加结点的方法如下:
#include <iostream>
#include <cassert>
using namespace std;
template<typename T>
class Node {
public:
T e;
Node<T>* next;
//Node(T e, Node *next) : e(e), next(next) {}
//Node(T e) : e(e), next(nullptr) {}
//Node() : e(), next(nullptr) {}
Node(T e,Node* next){
this->e = e;
this->next = next;
}
Node(T e) {
this->e = e;
this->next = nullptr;
}
Node() {
this->e = NULL;
this->next = nullptr;
}
};
template<typename T>
class LinkedList {
private:
int size;
Node<T>* dummyHead;
public:
LinkedList(){
//初始化链表,创建一个结点(称之为头结点),使得指针 dummyHead 指向这个头结点(头结点不包含在链表中)。此时可以用dummyHead 指代头结点。
//头结点不存储数据,初始化的时候不需要赋予数据。头结点此时也不执行任何结点,头结点的指针为null。
dummyHead = new Node<T>();
size = 0;
}
int getSize() {
return size;
}
bool isEmpty() {
return size == 0;
}
void add(int index,T e) {
assert(index >= 0 && index <= size);
//创建一个新的指针,这个指针指向头结点
Node<T>* prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
/* 进入循环的时候,pre指向头结点。
指向完 i=0 的循环,pre指向链表 下标=0 的位置。
那么执行完 i=index-1 的循环,pre指向链表中 下标=index-1的位置,刚刚好就是index的前一个位置。
*/
// Node node = new Node(e);
// node.next = pre.next;//pre.next 指向 下标 = index 位置,此时node.next 也指向 下标 = index 位置。
// pre.next = node;//index-1 位置的结点 pre 的指针 next 指向node结点,那么此时node结点就处于index位置
//以上3句可以使用下面一句替代
prev->next = new Node<T>(e, prev->next);
size++;
}
void addFirst(T e) {
add(0, e);
size++;
}
void addLast(T e) {
add(size, e);
}
/**
* 注意区分上面的Node pre = dummyHead 与下面的 Node cur = dummyHead.next
* 由于上面我们是要添加结点,必须找到结点的前一个元素,而对于index=0的结点,它的前一个元素是头结点dummyHead,
* 因此我们要从 dummyHead 位置,既链表第一个结点的前一个位置开始遍历,否则会错过链表的第一个结点。
*
* 而下面的get、set、contain 操作都是要找到 index 位置的结点,不需要找到 index位置结点的前一个结点。
* 因此,我们可以直接从链表的第一个结点 dummyHead.next 开始查找。
*/
T get(int index) {
assert(index >= 0 && index <= size);
//头结点的指针:dummyHead.next 指向链表第一个结点,我们新创建一个指针 cur,同样指向链表的第一个结点。
Node<T>* cur = dummyHead->next;
/*
进入循环,cur指向链表 下标=0 的位置的结点。
经历 i=0 的循环后,cur指向链表 下标=1 位置的结点。
经历 i=index-1 的循环后,cur执行向链表 下标=index 位置的结点。
*/
for (int i = 0; i < index; i++) {
cur = cur->next;
}
return cur->e;
}
T getFirst() {
return get(0);
}
T getLast() {
return get(size - 1);
}
void set(int index,T e) {
assert(index >= 0 && index <= size);
Node<T>* cur = dummyHead->next;
for (int i = 0; i < index; i++) {
cur = cur->next;
}
cur->e = e;
}
bool find(T e) {
Node<T>* cur = dummyHead->next;
while (cur != NULL) {
if (cur->e == e) {
return true;
}
else {
cur = cur->next;
}
}
return false;
}
T remove(int index) {
assert(index >= 0 && index <= size);
if (!(index >= 0 && index <= size)) {
cout << "index error" << endl;
}
else {
Node<T>* prev = dummyHead;
for (int i = 0; i < index; i++) {
prev = prev->next;
}
Node<T>* retNode = prev->next;
prev->next = retNode->next;
retNode->next = nullptr;
size--;
return retNode->e;
}
}
T removeFirst() {
return remove(0);
}
T removeLast() {
return remove(size - 1);
}
/**
* 想要删除数据是元素 e 的结点,我们必须先找到这个结点,然后按上面的删除方法对其进行删除。
* 上面查找链表是否包含元素 e 的方法contains 是从链表第一个元素开始查找。但是由于我们这里不仅仅需要查找到这个元素,
* 而且还要删除这个元素,我们就必须找到这个元素的前一个元素。那么我们从虚拟头结点 dummyHead开始寻找,
* 使用 node.next 判断结点 node.next是否包含元素e,包含就通过 node结点删除 node.next结点。(node结点是要删除结点 node.next 的前一个结点)
*/
void removeElement(T e) {
if (!find(e)) {
cout << "error,don't find element" << endl;
}
else {
Node<T>* prev = dummyHead;
while (prev->next != nullptr) {
if (prev->next->e == e) {
break;
}
else {
prev = prev->next;
}
}
//跳出循环有可能因为遍历到最后一个结点,既pre.next=null,也有可能是因为找到相应节点:pre.next.e.equals(e)
//因此我们这里还需进行判断,当 pre.next != null,说明找到相应节点,再进行删除操作
if (prev->next != nullptr) {
Node<T>* delNode = prev->next;
prev->next = delNode->next;
delNode->next = nullptr;
size--;
}
}
}
void print() {
Node<T>* cur = dummyHead->next;
while (cur != NULL) {
cout << cur->e << "->";
cur = cur->next;
}
cout << "NULL" <<" size:"<<getSize()<< endl;
}
};
事件复杂度分析
//添加——总体来说,添加事件复杂度为O(n)
addLast(e):O(n)、addFirst(e):O(1);add(index,e):O(n/2)=O(n)
//删除——总体来说,删除事件复杂度为O(n)
removeLast(e):O(n);removeFirst(e):O(1);remove(index,e):O(n/2)=O(n)
//修改操作
set(index,e):O(n)
//查找操作——总体来说,查找事件复杂度为O(n)
get(index):O(n);contains(e):O(n);
之前数组有一个查找元素 e的下标的函数:find(e):O(n),对于链表来说,这种方法没用,因为链表即使拿到索引,我们也得从头开始遍历,无法快速访问。因此链表中没有实现这个方法。
2、用链表实现栈
我们只对链表头进行操作,时间复杂度都是O(1),这个特性与栈相同。那么我们就可以使用链表作为底层来实现栈。
#pragma once
#include"LinkedList.h"
#include"Stack.h"
template<typename T>
class LinkedListStack : public Stack<T>{
private:
LinkedList<T>* linkedlist;
public:
LinkedListStack() {//链表栈的底层实现是链表,没有容积这个概念
linkedlist = new LinkedList<T>();
}
int getSize() {
return linkedlist->getSize();
}
bool isEmpty() {
return linkedlist->isEmpty();
}
//----------------------------------------------------出栈,入栈、获取栈顶元素——链表头既栈顶
void push(T e) {
return linkedlist->addFirst(e);
}
T pop() {
return linkedlist->removeFirst();
}
T peek() {
return linkedlist->getFirst();
}
void print() {
linkedlist->print();
}
};
3、用链表实现队列
我们使用链表来实现队列,由于链表在尾部进行增删的时候,事件复杂度是O(n),而使用链表实现队列,增删中必然有一步是对链表的尾部进行操作,这使得链表实现的队列时间复杂度过高。之前在使用数组实现队列的时候,也会遇到这种增删操作必然有一步时间复杂度是O(n)的情况,既在数组头部进行增删的时候,时间复杂度是O(n)。
因此,我们之前为了解决这个问题,使用循环队列的方式解决。循环队列也是使用数组实现,但是它没有直接复用我们所编写的数组类 Array ,而是使用java所提供的数组,基于java的数组,使用2个标志来标示队列的首尾,因此使得在数组头插入数据的时间复杂度变成O(1)。
如果我们想使用链表来实现队列,不能直接使用我们创建的链表类 LinkedList 来实现,因为使用 LinkedList链表 直接实现队列,会导致 队列的增删操作必然有一个是O(n) 复杂度。因此,我们要使用 循环队列的思想 以及 链表的思想 来实现 链表类的队列 LinkedListQueue。
我们通过自己创建的 Node 结点,附加标志首尾的front与tail,这样就可以使用实现
//#include"LinkedList.h"
#pragma once
#include"Queue.h"
using namespace std;
#include <iostream>
template<typename T>
class Node {
public:
T e;
Node<T>* next;
//Node(T e, Node *next) : e(e), next(next) {}
//Node(T e) : e(e), next(nullptr) {}
//Node() : e(), next(nullptr) {}
Node(T e, Node* next) {
this->e = e;
this->next = next;
}
Node(T e) {
this->e = e;
this->next = nullptr;
}
Node() {
this->e = NULL;
this->next = nullptr;
}
};
template<typename T>
class LinkedListQueue :public Queue<T> {
private:
//LinkedList<T>* linkedlist;
Node<T>* head;
Node<T>* tail;
int size;//这里的size要的,不管你linkedlist里有没有,
public:
LinkedListQueue() {
size = 0;
head = nullptr;
tail = nullptr;
}
int getSize() {
return size;
}
bool isEmpty() {
return size == 0;
}
void enqueue(T e) {
//当 tail=null,说明链表是空的
if (tail == nullptr) {
//创建一个新的结点,并使得tail指向这个结点。那么这个结点暂时可以称为 tail
tail = new Node<T>(e);
//同时注意,这个结点也是链表的第一个结点,那么我们使得指向链表第一个结点的指针head指向这个新结点 tail
head = tail;
}
else {
tail->next = new Node<T>(e);
tail = tail->next;
}
size++;//最后将链表长度加1
}
T dequeue() {
if (size == 0 || head==nullptr) {
cout << "isEmpty\n";
}
else {
// 出队方法:dequeue,从链表的头部出队,需要对head进行操作
Node<T>* retNode = head;
head = head->next;
//cout << "head test:" << (head==nullptr) << endl;
retNode->next = nullptr;
if (head == nullptr) {
tail = nullptr;
}
size--;
return retNode->e;
}
}
T getFront() {
if (size == 0) {
cout << "isEmpty";
}
else {
return head->e;
}
}
void print() const{
Node<T> *cur = head;//形参不匹配说的就是这里要用cur来代替head,不然你改了head前面就nullprt了
cout << "head: ";
while (cur != NULL) {
cout << cur->e << "->";
cur = cur->next;
//cout << " tail";
}
//cout << '\n';
cout << "NULL" << " size:" << size << endl;
}
};
本文详细介绍了链表的基本概念和实现方法,包括节点的添加、删除等操作,并探讨了链表在栈和队列数据结构中的应用。分析了各种操作的时间复杂度。
504

被折叠的 条评论
为什么被折叠?



