目录
一、线性表
二、顺序表
三、链表
一、线性表
线性表( linear list )是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列、字符串...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
其中数组(某种程度上可以认为是顺序表)在物理空间上是连续存储的,而链表是由不同的小块通过某些“记忆”顺次连接存储的,这些小块或连续,或不连续,但是在逻辑上都呈线性。
二、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
顺序表一般又可以分为静态顺序表和动态数据表:
静态数据表是用一段长度固定的物理空间来对数据进行存储,数组长度是固定的。堆开辟的数组在特殊情景下,使用比较多,尤其是动态顺序表。
// 静态顺序表:通过宏来替换定义一个数组
#define N 10
struct Arr
{
public:
int n;
int arr[N];
};
// 动态顺序表:在数组长度不确定的时候,采用堆空间来创建顺序表
class Arr
{
public:
Arr(int _n):n(_n),arr(new int[n]){}
protected:
int n;
int* arr;
};
显然,通过宏替换或者是栈上直接开辟的数组对于长度是已经锁死了的,当数据足够庞大的时候,N为10不够,这时需要修改为比较大的值。如果是本来就给N定为比较大的数,在面对较少的数据时就会显得浪费空间。所以在实践中,我们更推荐使用的是动态数组。动态数组可以实现在数组长度达到最大时扩容继续添加数据的作用。
下面是一串采用C++实现的顺序表的代码:
(1)SeqList.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cassert>
using namespace std;
class SeqList
{
public:
//初始化
SeqList();
//销毁
~SeqList();
//头插
void PushFront(int data);
//头删
void PopFront();
//尾插
void PushBack(int data);
//尾删
void PopBack();
//打印
void Print();
//寻找
int* Find(int data);
//插入
void Insert(int pos, int data);
//清除
void Erase(int data);
//检查容量
void Check();
private:
int* ptr;
int index;
int capacity;
};
(2)SeqList.cpp
#include "SeqList.h"
SeqList::SeqList()
{
ptr = nullptr;
index = 0;
capacity = 0;
}
SeqList::~SeqList()
{
if (ptr!=nullptr)
{
delete[]ptr;
ptr = nullptr;
capacity = 0;
index = 0;
cout << "~SeqList Destroyed" << endl;
}
}
void SeqList::PushFront(int data)
{
Check();
for (int i = index - 1; i >= 0; i--)
{
ptr[i + 1] = ptr[i];
}
ptr[0] = data;
index++;
}
void SeqList::PopFront()
{
for (int i = 1; i < index; i++)
{
ptr[i - 1] = ptr[i];
}
index--;
}
void SeqList::PushBack(int data)
{
Check();
assert(ptr);
ptr[index++] = data;
}
void SeqList::PopBack()
{
assert(index >= 0);
index--;
}
void SeqList::Print()
{
for (int i = 0; i < index; i++)
{
cout << ptr[i] << " ";
}
cout << endl;
}
int* SeqList::Find(int data)
{
for (int i = 0; i < index; i++)
{
if (ptr[i] == data)
{
return ptr + i;
}
}
return nullptr;
}
void SeqList::Insert(int pos, int data)
{
assert(pos > 0);
Check();
for (int i = index - 1; i >= pos - 1; i--)
{
ptr[i + 1] = ptr[i];
}
ptr[pos-1] = data;
index++;
}
void SeqList::Erase(int data)
{
int flag = 1;
for (int i = 0; i < index; i++)
{
if (ptr[i] == data)
{
for (int j = i; j <index; j++)
{
ptr[j] = ptr[j + 1];
}
index--;
flag = 0;
}
}
if (flag)
{
cout << "No Data" << endl;
}
}
void SeqList::Check()
{
if (ptr == nullptr || index == capacity)
{
int new_capcaity = (ptr == nullptr ? 4 : capacity * 2);
if (capacity != new_capcaity && capacity != 0)
{
int* temp = new int[new_capcaity];
for (int i = 0; i < index; i++)
{
temp[i] = ptr[i];
}
delete[] ptr;
ptr = temp;
assert(ptr);
capacity = new_capcaity;
return;
}
ptr = new int[new_capcaity];
capacity = new_capcaity;
assert(ptr);
}
}
值得注意的是,如果使用模板template不能将函数定义与声明分离放在两个文件中,否则会导致很多的麻烦。
顺序表在实现上是比较简单的,但是在某些方面上效率是比较低的。比如在进行增加数据的操作时,如果数组已经满了,那么需要开辟新空间,并且将原先的数据挪到新空间,并释放旧空间;然后在进行扩容的操作时,一般是扩2倍,导致资源利用率会随着数据的减少而减少;在进行头插和中间位置的插入时,时间复杂度为O(N)。
三、链表
链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
1.单向不循环链表
// 不带哨兵位的单向链表
#include <iostream>
#include <cassert>
using namespace std;
template<class T>
struct Node
{
Node<T> *next;
T data;
Node(T _data) : data(_data),next(nullptr){}
};
template<class T>
class SLTList
{
protected:
Node<T> *pHead;
public:
SLTList():pHead(nullptr){}
void push_back(T data)
{
// 头为空就直接改变头指向
if(!pHead)
pHead = new Node<T>(data);
// 头不为空就尾插
else
{
Node<T> *tail = pHead;
while(tail->next)
{
tail = tail->next;
}
tail->next = new Node<T>(data);
}
}
void pop_back()
{
// 头为空就直接返回
if(!pHead)
return;
// 只有一个头结点,就释放该节点的权限并置空
if(!pHead->next)
{
delete pHead;
pHead = nullptr;
return;
}
Node<T> *fast = pHead;
Node<T> *slow = pHead;
while(fast->next)
{
slow = fast;
fast = fast->next;
}
slow->next = nullptr;
delete fast;
fast = nullptr;
}
void push_front(T data)
{
// 头插 更改头指针的指向
Node<T> *newNode = new Node<T>(data);
newNode->next = pHead;
pHead = newNode;
}
void pop_front()
{
// 如果头为空,就直接返回
if(!pHead)
return;
// 只有一个头结点,那就删除头结点,并把pHead置为空
if(!pHead->next)
{
delete pHead;
pHead = nullptr;
return;
}
// 头非空,就创建一个新头指针,释放旧头指针,最后改变指向
Node<T> *newHead = pHead->next;
delete pHead;
pHead = newHead;
}
void print()
{
// 打印输出链表的元素
Node<T> *tail = pHead;
while(tail)
{
cout << tail->data << endl;
tail = tail->next;
}
}
Node<T>* find(T data)
{
Node<T> *tail = pHead;
while(tail)
{
if(tail->data==data)
return tail;
tail = tail->next;
}
return nullptr;
}
void insert(Node<T>* pos,T data)
{
if(!pos&&pos!=pHead)
return;
if(pos==pHead)
{
push_front(data);
}
else
{
Node<T> *prePos = pHead;
while(prePos->next!=pos)
{
prePos = prePos->next;
}
Node<T> *newNode = new Node<T>(data);
prePos->next = newNode;
newNode->next = pos;
}
}
void erase(Node<T>* pos)
{
if(!pos)
return;
else
{
if(pos==pHead)
{
pop_front();
return;
}
Node<T> *prePos = pHead;
while(prePos->next!=pos)
{
prePos = prePos->next;
}
prePos->next = pos->next;
delete pos;
}
}
};
// 带哨兵位的单向链表
#include <iostream>
#include <cassert>
using namespace std;
// 带有哨兵位的单向链表
template <class T>
struct Node
{
T data;
Node<T> *next;
Node(T _data = 0) : data(_data), next(nullptr) {}
};
template <class T>
class SLTList
{
protected:
Node<T> *pHead;
public:
SLTList() : pHead(new Node<T>) {}
~SLTList()
{
Node<T> *tail = pHead;
while (tail->next)
{
Node<T> *tmp = tail;
tail = tail->next;
delete tmp;
tmp = nullptr;
}
}
// 尾插
void push_back(T _data)
{
assert(pHead);
Node<T> *tail = pHead;
while (tail->next != nullptr)
{
tail = tail->next;
}
tail->next = new Node<T>(_data);
}
// 尾删
void pop_back()
{
Node<T> *fast = pHead;
Node<T> *slow = pHead;
while (fast->next != nullptr)
{
slow = fast;
fast = fast->next;
}
// 不能删除哨兵位
if (fast != pHead)
delete fast;
slow->next = nullptr;
}
// 头插
void push_front(T _data)
{
if (pHead)
{
Node<T> *oldTail = pHead->next;
pHead->next = new Node<T>(_data);
pHead->next->next = oldTail;
}
}
// 头删
void pop_front()
{
if (pHead && pHead->next)
{
Node<T> *tmp = pHead->next;
pHead->next = pHead->next->next;
delete tmp;
}
}
// 寻找_data的元素位置
Node<T> *find(T _data)
{
assert(pHead);
Node<T> *pos = pHead->next;
while (pos)
{
if (pos->data == _data)
return pos;
pos = pos->next;
}
return nullptr;
}
// pos前面插入
void insert(Node<T> *pos, T _data)
{
assert(pHead);
Node<T> *prePos = pHead;
while (prePos->next != pos)
{
prePos = prePos->next;
}
prePos->next = new Node<T>(_data);
prePos->next->next = pos;
}
// 删除某个位置的元素
void erase(Node<T> *pos)
{
assert(pHead);
if (pos)
{
Node<T> *prePos = pHead;
while (prePos->next != pos)
{
prePos = prePos->next;
}
prePos->next = pos->next;
delete pos;
pos = nullptr;
}
}
void print()
{
assert(pHead);
Node<T> *tail = pHead->next;
while (tail)
{
cout << tail->data << endl;
tail = tail->next;
}
}
};
2.带头双向循环链表
#include <iostream>
#include <cassert>
using namespace std;
struct ListNode
{
ListNode *prev;
ListNode *next;
int data;
};
ListNode *ListCreate(int data)
{
ListNode *newNode = new ListNode;
if (newNode)
{
newNode->data = data;
newNode->prev = nullptr;
newNode->next = nullptr;
return newNode;
}
return nullptr;
}
void ListPrint(ListNode *pHead)
{
assert(pHead);
ListNode *tmp = pHead->next;
cout << "哨兵位<=>";
while (tmp != nullptr && tmp != pHead)
{
cout << tmp->data << "<=>";
tmp = tmp->next;
}
cout << endl;
}
void ListPushBack(ListNode *pHead, int data)
{
assert(pHead);
ListNode *tmp = ListCreate(data);
if(pHead->next!=nullptr)
{
ListNode *tail = pHead->prev;
tail->next = tmp;
tmp->prev = tail;
tmp->next = pHead;
pHead->prev = tmp;
}
else
{
pHead->next = tmp;
pHead->prev = tmp;
tmp->next = pHead;
tmp->prev = pHead;
}
}
void ListPopBack(ListNode*pHead)
{
assert(pHead);
if(pHead->next==nullptr||pHead->next==pHead->prev&&pHead->next==pHead)
{
cout << "Doubly linked list is empty!" << endl;
return;
}
ListNode* oldTail = pHead->prev;
ListNode *newTail = oldTail->prev;
newTail->next = pHead;
pHead->prev = newTail;
delete oldTail;
oldTail = nullptr;
}
void ListPushFront(ListNode*pHead,int data)
{
assert(pHead);
if(pHead->next)
{
ListNode *newHead = ListCreate(data);
ListNode *oldHead = pHead->next;
newHead->next = oldHead;
newHead->prev = pHead;
oldHead->prev = newHead;
pHead->next = newHead;
}
else
{
ListNode *newHead = ListCreate(data);
pHead->next = newHead;
pHead->prev = newHead;
newHead->next = pHead;
newHead->prev = pHead;
}
}
void ListPopFront(ListNode*pHead)
{
assert(pHead);
if(pHead->next==nullptr||pHead->next==pHead->prev&&pHead->next==pHead)
{
cout << "Doubly linked list is empty!" << endl;
return;
}
ListNode *oldHead = pHead->next;
ListNode *newHead = oldHead->next;
pHead->next=newHead;
newHead->prev = pHead;
delete oldHead;
oldHead = nullptr;
}
ListNode* ListFind(ListNode* pHead, int x)
{
assert(pHead);
ListNode *tail = pHead->next;
while(tail!=pHead)
{
if(tail->data==x)
{
return tail;
}
tail = tail->next;
}
return nullptr;
}
void ListInsert(ListNode* pos, int x)
{
ListNode *newNode = ListCreate(x);
ListNode *prevNode = pos->prev;
prevNode->next = newNode;
newNode->prev = prevNode;
newNode->next = pos;
pos->prev = newNode;
}
void ListErase(ListNode*pos)
{
ListNode *prevNode = pos->prev;
ListNode *nextNode = pos->next;
prevNode->next = nextNode;
nextNode->prev = prevNode;
delete pos;
pos = nullptr;
}
对应STL里面的List: http://t.csdnimg.cn/xFva4 。
单链表的实现相对容易一点,但是在实现尾插和尾删的效率上是比较低的。而双向带头循环链表虽然实现起来相对复杂,但是在使用上却展示出了许多的优势。