数据结构篇之线性表
线性表
定义:
- 我们将处理一般的形如A1,A2,A3...AN的表成为线性表 ,N为表的大小,我们称N=0的表为空表。对于除空表外的任何表,我们说A~i+1~后继A~i~并称A~i-1~前驱A~i~,表中的第一个元素为A~1~,而最后一个元素为A~N~.
简单数组实现表
概念:
- 表的实现可以通过数组进行实现,而表的插入和删除,就需要对数组元素进行移动,例如插入,如果向第一个位置插入,我们就需要数组元素都向后移动一位,又如删除第一个元素,就需要将第一个元素删掉,数组元素再向前移动一位。
cpp
#include <stdio.h?>
#define SIZE 100
struct List{
int elements[SIZE];
int len; //表中元素的长度(即个数)
};
//创建一个空表对象
struct List* createList(){
struct List *p;
p=malloc(sizeof(struct List));
p->len=0;
return p;
}
//判断表是否为空
bool isEmpty(struct List* p){
return p->len==0;
}
//头插法
void inserthead(struct List *p,int x){
if(p->len>=SIZE){
printf("表已满");
return;
}
for(int i=p->len;i>0;i--){
p->elements[i]=p->elements[i-1];
}
p->elements[0]=x;
p->len++;
return;
}
//尾插法
void insertlast(struct List*p,int x){
if(p->len>=SIZE){
printf("表已满");
return;
}
p->elements[p->len]=x;
p->len++;
return;
}
//打印表中数据
void printList(struct List *p){
for(int i=0;i<p->len;i++){
printf("list element is:%d\n",p->elements[i]);
}
}
//查找指定值,返回索引
int find(struct List *p,int x){
for(int i=0;i<p->len;i++){
if(p->elements[i]==x){
return i;
}
}
printf("没有找到值为%d的数据\n",x);
return NULL;
}
//查找表中对应索引的值,返回值
int findkth(struct List *p,int pos){
if(pos>p->len){
printf("不存在索引为%d的数据\n",pos);
return NULL;
}
return p->elements[pos];
}
//删除
void delete(struct List *p,int x){
for(int i=0;i<p->len;i++){
if(p->elements[i]==x){
for(int j=i;j<p->len-1;j--){
p->elements[j]=p->elements[j+1];
}
p->len--;
break;
}
}
}
链表
与简单数组实现的优缺点:
- 优点 :简单数组实现表的插入和删除需要花费大量的时间,尤其当数据量大的时候,则程序的耗时将会难以接受,因此为了避免插入和删除的线性开销,我们需要允许表可以不连续存储,否咋表的部分或全部需要整体移动,因此我们将用链表进行实现
- 缺点:在查找数据的时候,链表所耗的时长更多,因为数组可以直接用索引来获取,但是链表需要遍历表连接的数据,直到找到数据为止。
概念:
- 链表 由一系列不必在内存相连的结构 组成,每一个结构均含有表元素和指向包含该元素后继元的结构的指针,我们称之为Next指针。最后一个单元的Next指针指向NULL,NULL是在c中的定义,c中规定为0.
cpp
struct Node{
int data;
struct Node* next;
};
struct Node *createNode(int data){
struct Node *p=(struct Node* )malloc(sizeof(struct Node));
p->data=data;
p->next=NULL;
return p;
}
//头插法
struct Node *inserthead(struct Node *head, int data) {
struct Node *add = createNode(data);
if (head == NULL) {
return add;
}
// struct Node *p = head;
// head = add;
// head->next = p;
// return head;
add->next = head;
return add;
}
//尾插法
struct Node *insert(Node *head,int data){
struct Node *add=createNode(data);
if(head==NULL){
return add;
}
while(head->next!=NULL){
head=head->next;
}
head->next=add;
return head;
}
struct Node *search(Node *head,int data){
if(head==NULL||head->data==data){
return head;
}
struct Node *p=head;
while(p->next!=NULL){
if(p->data==data){
return p;
}
p=p->next;
}
return NULL;
}
struct Node *del(Node *head,int data){
if(head==NULL){
return head;
}
if(head->data==data){
struct Node *p=head->next;
free(head);
return p;
}
struct Node *tail=head;
struct Node *p=head->next;
while(p!=NULL){
if(p->data==data){
break;
}
tail=p;
p=p->next;
}
if(p!=NULL&&p->data==data){
tail->next=p->next;
free(p);
}
return head;
}
//清除链表
struct destoty(Node *head){
if(head!=NULL){
destory(head->next);
free(head);
}
}
双向非循环链表
作用:
- 有时候以倒序扫描链表更方便 ,但是为了实现这个就会多出一条附加链的开销,他增加了空间的需求 ,同时使得插入和删除的开销增加一倍,但是又简化了删除操作。
概念:
- 双向非循环链表 就是双向的链表,链表中的前后结点都有指向对方的指针,就是A~1~的next为A~2~,但是A~2~又有指针指向A~1~.
代码:
cpp
struct doublyNode{
int data;
struct doublyNode* next;
struct doublyNode* prev;
};
struct doublyNode *createNode(int data){
auto* p=(struct doublyNode*)malloc(sizeof(struct doublyNode));
p->data=data;
p->prev=NULL;
p->next=NULL;
return p;
}
//头插法
struct doublyNode*inserthead(struct doublyNode* head,int data){
struct doublyNode*add= createNode(data);
if(head==NULL){
return add;
}
struct doublyNode*p=head;
head=add;
head->next=p;
p->prev=head;
return head;
}
//尾插法
struct doublyNode * insertlast(struct doublyNode* head,int data){
struct doublyNode* add= createNode(data);
if(head==NULL){
return add;
}
struct doublyNode*p=head;
while (p->next!=NULL){
p=p->next;
}
p->next=add;
p->prev=p;
return head;
}
//删除
doublyNode * del(struct doublyNode* head,int data){
if(head==NULL){
cout<<"链表没有数据"<<endl;
return head;
}
if(head->data==data){
struct doublyNode* p=head->next;
free(head);
return p;
}
struct doublyNode* tail=head;
struct doublyNode* p=head->next;
while (p!=NULL){
if(p->data==data){
tail->next=p->next;
free(p);
return head;
}
tail=p;
p=p->next;
}
}
void printlist(struct doublyNode* head){
if(head==NULL){
cout<<"链表没有数据"<<endl;
}
struct doublyNode* p=head;
while (p!=NULL){
cout<<p->data<<" ";
p=p->next;
}
cout<<endl;
}
循环链表
概念:
- 循环链表 就是没有表头表尾的链表,例如一般的链表的表尾为NULL,但是循环链表的下一个指向的表头,不仅有单向循环链表 ,而且还有双向循环链表,双向循环链表就是表头的前一个指向表尾,表尾的下一个指向表头。
- C++中的STL库中就已经封装了双向循环链表的类(list)
代码:
cpp
//单向循环链表
struct CirNode{
int data;
struct CirNode* next;
struct CirNode *prev;
};
struct CirNode* createNode(int data){
struct CirNode* p=(struct CirNode*)malloc(sizeof(struct CirNode));
p->data=data;
p->next=NULL;
p->prev=NULL;
return p;
}
struct CirNode* inserthead(CirNode* head,int data){
struct CirNode* add= createNode(data);
if(head==NULL){
add->next=add;
add->prev=add;
return add;
}
struct CirNode* p=head;
struct CirNode* p1=head->next;
while (p1!=head){
p1=p1->next;
}
head=add;
head->next=p;
p1->next=head;
head->prev=p1;
p->prev=head;
return head;
}
//尾插法
struct CirNode* insertlast(CirNode* head,int data){
struct CirNode* add= createNode(data);
if(head==NULL){
add->prev=add;
add->next=add;
return add;
}
struct CirNode* p=head;
while(p->next!=head){
p=p->next;
}
p->next=add;
add->prev=p;
add->next=head;
return head;
}
struct CirNode* del(CirNode* head,int data){
if(head==NULL){
cout<<"链表没有数据"<<endl;
return head;
}
if(head->data==data){
struct CirNode* p=head->next;
p->prev=head->prev;
free(head);
return p;
}
struct CirNode* tail=head;
struct CirNode* p=head->next;
while (p!=head){
if(p->data==data){
tail->next=p->next;
p->next->prev=tail;
free(p);
return head;
}
tail=p;
p=p->next;
}
}
void printlist(CirNode* head){
if(head==NULL){
cout<<"链表没有数据"<<endl;
}
struct CirNode* p=head;
do{
cout<<p->data<<" ";
p=p->next;
} while (p!=head);
cout<<endl;
}
跳跃表(跳表)
概念:
- 跳跃表是一个随机化的数据结构 ,实质就是一种可以进行二分查找的有序链表。
- 跳表在原有的有序链表的基础上面通过随机化算法产生随机级数的节点使其通过节点的索引来实现快速查找 ,提高了插入,删除,查找操作的性能
- 跳表数据结构是以O(logN)期望时间进行查找和删除操作的数据结构
特性:
- 有序性
- 插入新元素通过随机化算法随机分配存储该新元素值的节点级数 ,而级数就代表着这个节点有多少个指针 ,例如调表的第k位4阶节点,从最高层到最后一层,每个指针指向第2^i^+k节点(也就是跳过2^i^个节点,i为层数)
随机化算法:
- 该算法用于给跳表插入新元素时,进行随机产生k阶节点用于作为新元素值得载体插入在跳表中。
- 虽然是随机的,但是各阶节点的产生是有概率性的,1/2是一阶节点,1/4是2阶节点,大约1/2^i^的节点是i阶节点。
查找操作:
- 首先在最高级索引上查找最后一个小于当前查找元素的位置 ,然后再跳到次高级索引继续查找,直到跳到最底层为止。
- 跳表的数据结构大大提高了查找操作,跳表的查找操作类似于二分查找。
插入操作:
-
随机产生阶数,然后根据阶数跟数值创建节点
-
如果新创建的表头 ,则直接令表头的第一层索引指针指向新创建的节点 ,如果不是就根据上步产生的阶数创建prev数组用来存储每层要插入节点位置的前继节点,遍历每一层,将prev赋值完。
-
然后根据阶数遍历,插入到存储到prev的前继节点的下一个位置。
删除操作:
- 跟插入操作差不多,先进行将prev数组赋值
- 然后遍历每层,将每层的前继节点指向被删除节点的下一个节点。
- 释放被删除节点的空间。
代码:
cpp
struct Node{
int val; //存储元素值
vector<Node*>next; //存储各层数指针的数组
int level; //阶数
};
class skiplist{
public:
skiplist(int level):Maxlevel(level){
head= createNode(NULL,Maxlevel);
}
~skiplist() {
Node* tail=head;
Node* p;
while (tail!= nullptr){
p=tail;
tail=tail->next[0];
delete p;
}
head= nullptr;
};
Node* find(int data);
void insert(int data);
void del(int data);
void print();
private:
Node* head;
int Maxlevel;
Node* createNode(int data,int level){
auto p = new Node;
p->val=data;
p->level=level;
p->next.resize(level,NULL);
return p;
}
int rand_level(){
int level=1;
while(rand()%2&&level<=Maxlevel){
level++;
}
return level;
}
};
Node *skiplist::find(int data) {
Node* p=head;
for(int i=Maxlevel-1;i>=0;i--){
while (p->next[i]!= nullptr&&p->next[i]->val<data){
p=p->next[i];
}
}
if(p->next[0]!= nullptr&&p->next[0]->val==data){
return p->next[0];
}
return nullptr;
}
void skiplist::insert(int data) {
int level=rand_level();
Node* add= createNode(data,level);
if(head->next[0]== nullptr){
head->next[0]=add;
return;
}
Node* p=head;
Node* prev[level];
for(int i=level-1;i>=0;i--){
while (p->next[i]!= nullptr&&p->next[i]->val<data){
p=p->next[i];
}
prev[i]=p;
}
for(int i=0;i<level;i++){
add->next[i]=prev[i]->next[i];
prev[i]->next[i]=add;
}
}
void skiplist::del(int data) {
Node* p=head;
Node* prev[Maxlevel];
for(int i=Maxlevel-1;i>=0;i--){
while (nullptr!=p->next[i]&&p->next[i]->val<data){
p=p->next[i];
}
prev[i]=p;
}
if(p->next[0]!= nullptr&&p->next[0]->val==data){
Node* tail=p->next[0];
for(int i=Maxlevel-1;i>=0;i--){
if(prev[i]->next[i]!= nullptr&&prev[i]->next[i]->val==data)
prev[i]->next[i]=prev[i]->next[i]->next[i];
}
delete tail;
}
}
void skiplist::print() {
Node* p=head->next[0];
while (p!= nullptr){
cout<<p->val<<" ";
p=p->next[0];
}
cout<<endl;
}
尾言
完整版笔记也就是数据结构与算法专栏完整版可到我的博客进行查看,或者在github库中自取(包含源代码)
- 博客1: codebooks.xyz
- 博客2:moonfordream.github.io
- github项目地址:Data-Structure-and-Algorithms