数据结构笔记(其二)--线性表

目录

1. 基本概念

2.线性表的基本操作

3.顺序表

(1).静态分配

(2).动态分配

(3).顺序表的插入与删除(以静态分配为例)(示例代码中包含了一下必要的基本函数)

 (4).按位序查找、按值查找

4.链表

(1).单链表

i.单链表(带头结点)的定义

ii.插入、删除(带头结点)

iii.查找 

iiii.单链表的建立 

iiiii.完整的代码

(2).双链表

i.双链表的初始化

ii.插入、删除 

iii.双链表的遍历

(3).循环链表

i.循环单链表

ii.循环双链表

(4).静态链表 


1. 基本概念

线性表:

(1).其中的各个元素,数据类型相同

(2).元素之间,有次序

(3).都有表头元素表尾元素

(4).除了表头表尾,每个元素都可以找到一个直接前驱直接后继

2.线性表的基本操作

表的从无到有

InitList(&L):初始化一个线性表

DestroyList(&L):销毁

ListInsert(&L, i, e):插入,在表L的第 i 处插入元素 e

ListDelete(&L, i, &e):删除,将表L的第 i 处元素删除,并用 e 返回该被删除的元素

LocateElem(L, e):按查找,在表中查找特定关键字e 的元素,返回的是e 的位序,而非下标

GetElem(L, i):按查找,获取表中第 i 个元素的值

其他常规操作:

Length(L):求表长

PrintList(L):输出表的所有元素

Empty(L):判断表是否为空,返回true or false

高度概括,即为,创销与增删改查

3.顺序表

顺序存储方式储存数据的线性表

(1).静态分配

#define MAX 10
//顺序表(静态分配)
class SqList
{
public:
    int data[MAX];
    int length;
};
//初始化
void InitList(SqList &l)
{
    for(int i = 0 ;i < 10 ;i++)
    {
        l.data[i] = 0;
    }
    l.length = 0;
}
//打印所有元素
void PrintList(SqList &l)
{
    for (int i = 0; i < 10; i++)
        cout << "第" << i << "个数:" << l.data[i] << endl;
}

//测验
void test01()
{
    SqList l;
    InitList(l);
    PrintList(l);
}

(2).动态分配

#define InitSize 10
//顺序表(动态分配)
class SqList
{
public:
	int* data;		//指示动态分配数组的指针
	int MaxSize;	//指示最大容量
	int length;		//指示当前长度
};
//初始化顺序表
void InitList(SqList& l)
{
	l.data = new int[InitSize];
	l.MaxSize = InitSize;
	l.length = 0;
	for (int i = 0; i < l.MaxSize; i++)
	{
		l.data[i] = 0;
	}
}
//增长数组空间
void IncreaseSize(SqList& l, int len)
{
	int* p = l.data;					//暂存原数组中的数据
	l.data = new int[10 + len];			//扩展新的数组
	for (int i = 0; i < l.length; i++)	//将原数据拷贝进新数组中
	{
		l.data[i] = p[i];
	}
	l.MaxSize = InitSize + len;			//修改数组的状态数据
	delete p;							//将p释放
}
//打印所有元素
void PrintList(SqList& l)
{
	for (int i = 0; i < 10; i++)
		cout << "第" << i << "个数:" << l.data[i] << endl;
}

void test01()
{
	SqList l;
	InitList(l);
	PrintList(l);
}

(3).顺序表的插入与删除(以静态分配为例)(示例代码中包含了一下必要的基本函数)

//插入

bool ListInsert(SqList& l, int d, int e)
{
    if (l.length >= MAX)                        //首先要判断表是否已满、插入是否合法
    {
        cout << "插入失败,已达上限" << endl;
        return false;
    }
        
    if (d < 1 || d > l.length + 1)
    {
        cout << "插入失败,无直接前驱" << endl;
        return false;
    }
    for (int j = l.length; j >= d; j--)         //将插入点之后的元素后移
        l.data[j] = l.data[j - 1];
    l.data[d - 1] = e;                          //插入,因为d指的是第几个数,在数组的换算中要减一
    l.length++;
    return true;
}

//删除
bool ListDelete(SqList& l, int d, int &e)
{
    if (d < 1 || d >l.length)                   //判断删除的位置是否合法
        return false;
    e = l.data[d - 1];                          //暂存删除掉的元素
    for (int j = d; j < l.length; j++)          //将被删除元素之后的元素前移
        l.data[j - 1] = l.data[j];              //此处,必须是j = d,j-1被j覆盖,若j = d-1,则下文的覆盖会变为j 被j+1 覆盖,而j+1在最后有可能会超过数组的最大容量
    l.length--;
    return true;
}

示例代码

#define MAX 10
//顺序表(静态分配)
class SqList
{
public:
    int data[MAX];
    int length;
};
//初始化
void InitList(SqList& l)
{
    for (int i = 0; i < 10; i++)
    {
        l.data[i] = 0;
    }
    l.length = 0;
}
//打印所有元素
void PrintList(SqList& l)
{
    for (int i = 0; i < 10; i++)
        cout << "第" << i << "个数:" << l.data[i] << endl;
}
//存入数据
void InputElem(SqList& l, int e)
{
    int i = 0;
    while (i < MAX)
    {
        if (l.data[i] == 0)
        {
            l.data[i] = e;
            l.length++;
            break;
        }
        i++;
    }
}
//获取顺序表长度
int GetLength(SqList l)
{
    //cout << l.length << endl;
    return l.length;
}
//插入
bool ListInsert(SqList& l, int d, int e)
{
    if (l.length >= MAX)                        //首先要判断表是否已满、插入是否合法
    {
        cout << "插入失败,已达上限" << endl;
        return false;
    }
        
    if (d < 1 || d > l.length + 1)
    {
        cout << "插入失败,无直接前驱" << endl;
        return false;
    }
    for (int j = l.length; j >= d; j--)         //将插入点之后的元素后移
        l.data[j] = l.data[j - 1];
    l.data[d - 1] = e;                          //插入,因为d指的是第几个数,在数组的换算中要减一
    l.length++;
    return true;
}
//删除
bool ListDelete(SqList& l, int d, int &e)
{
    if (d < 1 || d >l.length)                   //判断删除的位置是否合法
        return false;
    e = l.data[d - 1];                          //暂存删除掉的元素
    for (int j = d; j < l.length; j++)          //将被删除元素之后的元素前移
        l.data[j - 1] = l.data[j];              //此处,必须是j = d,j-1被j覆盖,若j = d-1,则下文的覆盖会变为j 被j+1 覆盖,而j+1在最后有可能会超过数组的最大容量
    l.length--;
    return true;
}

//查看情况
void CheckList(SqList& l)
{
    PrintList(l);
    cout << "当前长度为" << GetLength(l) << endl;
}

//测验
void test01()
{
	SqList l;
	InitList(l);
    
    //输入部分数据
    InputElem(l, 1);
    InputElem(l, 2);
    InputElem(l, 3);
    InputElem(l, 4);
    CheckList(l);

    //开始插入
    if(ListInsert(l, 3, 6))
        CheckList(l);

    //开始删除
    int a = -1;
    if (ListDelete(l, 2, a))
        CheckList(l);
}

 (4).按位序查找、按值查找

        很简单,不多赘述

//判断d的合法性
bool JugdeD(SqList l, int d)
{
    if (d < 1 || d > l.length)
        return false;
    return true;
}

//按位序查找
int GetElem(SqList l, int d)
{
    if (JugdeD(l, d))
        return l.data[d - 1];
    return 0;
}

//按值查找
int LocateElem(SqList l, int e)
{
    for (int i = 0; i < l.length; i++)
    {
        if (l.data[i] == e)       //数组储存的数据,若是类等复杂的数据类型,则需要对等号进行重载
            return i + 1;
    }
    return 0;
}
//其余代码与上文相同
//其中,JugdeD函数可以替换上文插入与删除中对位序合法性的判别————封装

4.链表

链式存储方式储存数据的线性表

(1).单链表

i.单链表(带头结点)的定义

//单链表
class LNode
{
public:
    int data;           //数据域,存放数据
    LNode* next;        //指针域,指向下一个节点
};
//用using关键字给类起别名,用LinkList指代的是头结点,代表的是整个链表
using LinkList = LNode*;

//初始化
bool InitList(LinkList& L)
{
    L = new LNode();
    if (L == nullptr)   //如果成立,则说明内存不足,分配失败
        return false;
    L->next = nullptr;
    return true;
}

ii.插入、删除(带头结点)

不带头结点的,要注意头指针的变动,其他的都雷同。 

插入(普通版)

//插入
bool ListInsert(LinkList& L, int i, int e)
{
    if (i < 1)                          //判断插入位点是否合法[1]——i值的合法性
    {
        cout << "i为负数" << endl;
        return false;
    }    
    LNode* p = L;                       //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
    LNode* s = new LNode();             //新的数据储存
    s->data = e;
    while (p != nullptr && i != 1)      //由头结点起始,开始遍历寻找对应位点
    {
        p = p->next;
        i--;
    }
    if (p == nullptr)                   //判断插入的位点是否合法[2]——i值对应的节点的合法性
    {
        cout << "插入位点超出实际长度" << endl;
        return false;
    }                     
    s->next = p->next;                  //开始接轨,顺序不能乱
    p->next = s;
    return true;
}

插入(封装版)

//特定节点的后插操作
bool InsertNextNode(LNode* p, int e)
{
    if (p == nullptr)                   
    {
        cout << "插入位点超出实际长度" << endl;
        return false;
    }
    LNode* s = new LNode();
    s->data = e;
    s->next = p->next;
    p->next = s;
    return true;
}
//插入
bool ListInsert(LinkList& L, int i, int e)
{
    if (i < 1)                          //判断插入位点是否合法[1]——i值的合法性
    {
        cout << "i为负数" << endl;
        return false;
    }    
    LNode* p = L;                       //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
    while (p != nullptr && i != 1)      //由头结点起始,开始遍历寻找对应位点
    {
        p = p->next;
        i--;
    }
    return InsertNextNode(p, e);        //被封装了的部分
}

删除

//按位查找,获得第i个结点(此函数可以将上文的检索节点的部分替换)
LNode* GetElem(LinkList& L, int i)
{
	if (i < 0)
		return nullptr;
	LNode* p = L;
	while (p != nullptr && i != 0)
	{
		p = p->next;
		i--;
	}
	return p;
}
//删除节点
bool DeleteNode(LNode* p)
{
	if (p->next == nullptr)
	{
		free(p);
		return true;
	}
	else
	{
		LNode* q = p->next;         //用下一个节点q 将被删除节点p 覆盖,使p,q成为相同的节点,之后释放下一个节点
		p->data = p->next->data;
		p->next = q->next;
		free(q);
		return true;
	}
}
//删除:删除指定节点,并保留删除的数据
bool ListDelete(LinkList& L, int i, int& e)
{
	if (i < 1)						//判断删除的位置是否合法
		return false;
	LNode* p = GetElem(L, i - 1);
	LNode* q = GetElem(L, i);		//指向被删除的节点
	e = q->data;                    //储存被删除的数据
	p->next = q->next;              //绕过被删除节点,并释放被删除节点
	free(q);
	return true;
}

iii.查找 

其代码逻辑在“插入、删除”中已经有所体现

//按位查找,获得第i个结点
LNode* GetElem(LinkList& L, int i)
{
	if (i < 0)
		return nullptr;
	LNode* p = L;
	while (p != nullptr && i != 0)
	{
		p = p->next;
		i--;
	}
	return p;
}
//按值查找
LNode* LocateElem(LinkList L, int e)
{
	LNode* p = L->next;
	while (p != nullptr && p->data != e)
	{
		p = p->next;
	}
	return p;
}

iiii.单链表的建立 

建立一个单链表主要有两步:

Step 1:初始化

Step 2:将数据元素,存入链表(插入

初始化的函数在“定义”中已经给出

Step 2 中的存入方法主要有两种:尾插法头插法

尾插法

//尾插法建立单链表
LinkList List_TailInsert(LinkList& L)
{
	int x;
	InitList(L);
	LNode* s, * r = L;		//创建一套表尾指针
	cin >> x;
	while (x != 9999)		//设立一个截止输入,输入9999时跳出循环
	{
		s = new LNode();	//用s 建立新节点
		s->data = x;
		r->next = s;		//通过r 指针让节点指向下一个节点
		r = s;
		cin >> x;
	}
	cout << "输入循环已跳出" << endl;
	r -> next = nullptr;
	return L;
}

头插法

//头插法建立单链表
LinkList List_HeadInsert(LinkList& L)
{
	int x;
	InitList(L);
	LNode* s;
	cin >> x;
	while (x != 9999)		//设立一个截止输入,输入到9999时跳出循环
	{
		s = new LNode();	//用s 建立新节点
		s->data = x;
		s->next = L->next;	//
		L->next = s;
		cin >> x;			//使用此法,亦可将尾插得到的链表,逆置(反过来)
	}
	cout << "输入循环已跳出" << endl;
	
	return L;
}

iiiii.完整的代码

#include<iostream>
#include<string>
using namespace std;

//单链表
class LNode
{
public:
	int data;           //数据域,存放数据
	LNode* next;        //指针域,指向下一个节点
};
//用using关键字给类起别名,用LinkList指代的是头结点,代表的是整个链表
using LinkList = LNode*;

//初始化
bool InitList(LinkList& L)
{
	L = new LNode();
	if (L == nullptr)   //如果成立,则说明内存不足,分配失败
		return false;
	L->next = nullptr;
	return true;
}

//尾插法建立单链表
LinkList List_TailInsert(LinkList& L)
{
	int x;
	InitList(L);
	LNode* s, * r = L;		//创建一套表尾指针
	cin >> x;
	while (x != 9999)		//设立一个截止输入,输入到9999时跳出循环
	{
		s = new LNode();	//用s 建立新节点
		s->data = x;
		r->next = s;		//通过r 指针让节点指向下一个节点
		r = s;
		cin >> x;
	}
	cout << "输入循环已跳出" << endl;
	r -> next = nullptr;
	return L;
}

//头插法建立单链表
LinkList List_HeadInsert(LinkList& L)
{
	int x;
	InitList(L);
	LNode* s;
	cin >> x;
	while (x != 9999)		//设立一个截止输入,输入到9999时跳出循环
	{
		s = new LNode();	//用s 建立新节点
		s->data = x;
		s->next = L->next;	//
		L->next = s;
		cin >> x;			//使用此法,亦可将尾插得到的链表,逆置(反过来)
	}
	cout << "输入循环已跳出" << endl;
	
	return L;
}

//打印所有元素
void PrintList(LinkList& L)
{
	LNode* p = L;
	int i = 0;
	while (p != nullptr)
	{
		cout << "第" << i << "个节点的数据为" << p->data << endl;
		p = p->next;
		i++;
	}
	cout << "______________________________" << endl;
}
//存入数据(单纯的往后接数据)
void InputElem(LinkList& L, int e)
{
	LNode* p = new LNode();
	int i = 0;
	LNode* q = L;
	p->data = e;
	p->next = nullptr;
	while (1)
	{
		if (q->next != nullptr)
		{
			q = q->next;
		}
		else
		{
			q->next = p;
			break;
		}
	}
}

//后插操作:在特定节点之后插入节点
bool InsertNextNode(LNode* p, int e)
{
	if (p == nullptr)
	{
		cout << "插入位点超出实际长度" << endl;
		return false;
	}
	LNode* s = new LNode();
	s->data = e;
	s->next = p->next;
	p->next = s;
	return true;
}
//前插操作:在特定节点之前插入节点
bool InsertPriorNode(LNode* p, int e)
{
	if (p == nullptr)           //判别传入的p是否合法
	{
		cout << "插入位置不合法" << endl;
		return false;
	}
	LNode* s = new LNode();
	if (s == nullptr)           //判别内存是否分配成功,其实,每一个分配内存的变量后面,都应该判别一下是否分配成功
		return false;
	s->data = p->data;          //将p复制给s,让s 成为原来的p 节点
	s->next = p->next;
	p->next = s;                //让原来的p节点作为现在前插的节点
	p->data = e;
	return true;
}
//插入
bool ListInsert(LinkList& L, int i, int e)
{
	if (i < 1)                          //判断插入位点是否合法[1]——i值的合法性
	{
		cout << "i为负数" << endl;
		return false;
	}
	LNode* p = L;                       //让p与L指向相同的位点,L是指示头指针的,所以L是不能改变的
	while (p != nullptr && i != 1)      //由头结点起始,开始遍历寻找对应位点
	{
		p = p->next;
		i--;
	}
	return InsertNextNode(p, e);
}
//按位查找,获得第i个结点
LNode* GetElem(LinkList& L, int i)
{
	if (i < 0)
		return nullptr;
	LNode* p = L;
	while (p != nullptr && i != 0)
	{
		p = p->next;
		i--;
	}
	return p;
}
//按值查找
LNode* LocateElem(LinkList L, int e)
{
	LNode* p = L->next;
	while (p != nullptr && p->data != e)
	{
		p = p->next;
	}
	return p;
}
//删除节点
bool DeleteNode(LNode* p)
{
	if (p->next == nullptr)
	{
		free(p);
		return true;
	}
	else
	{
		LNode* q = p->next;         //用下一个节点q 将被删除节点p 覆盖,使p,q成为相同的节点,之后释放下一个节点
		p->data = p->next->data;
		p->next = q->next;
		free(q);
		return true;
	}
}
//删除:删除指定节点,并保留删除的数据
bool ListDelete(LinkList& L, int i, int& e)
{
	if (i < 1)						//判断删除的位置是否合法
		return false;
	LNode* p = GetElem(L, i - 1);
	LNode* q = GetElem(L, i);		//指向被删除的节点
	e = q->data;                    //储存被删除的数据
	p->next = q->next;              //绕过被删除节点,并释放被删除节点
	free(q);
	return true;
}

//获取顺序表长度
int GetLength(LinkList L)
{
	LNode* p = L;
	int cout = 0;
	p = p->next;			//因为有头结点,所以才有此行代码
	while (p != nullptr)
	{
		p = p->next;
		cout++;
	}
	return cout;
}





//测验
void test01()
{
	
	//用尾插法建立链表
	LinkList L;
	List_TailInsert(L);
	//List_HeadInsert(L);
	//检查数据
	PrintList(L);
	//插入
	ListInsert(L, 1, 999);
	ListInsert(L, 3, 888);
	PrintList(L);
	//删除
	int a = 0;
	ListDelete(L, 2, a);
	PrintList(L);
	cout << "a = " << a << endl;

}

int main()
{

	test01();

	system("pause");
	return 0;
}

(2).双链表

双链表和单链表的本质区别就是:双链表可以向前检索,单链表只能向后检索。

i.双链表的初始化

//双链表
class DNode
{
public:
	int data;				//数据域
	DNode* prior,* next;	//指针域,指向前驱与后继
};

using DLinkList = DNode*;

//初始化
bool InitDList(DLinkList& L)
{
	L = new DNode();
	if (L == nullptr)		//如果成立,则说明内存不足,分配失败
		return false;
	L->prior = nullptr;		//头结点的prior 永远指向NULL
	L->next = nullptr;
	return true;
}

ii.插入、删除 

        插入

//在p结点后插入s结点
bool InsertNextDNode(DNode* p, DNode* s)
{
	if (p == nullptr || s == nullptr)	//如果p 和s 中有任何一个是空的,那么插入都是没有意义的
		return false;
	s->next = p->next;					//先接入后继,再接入前驱,因为先接入前驱的话,会失去后继
	if (p->next != nullptr)				//双链表的特性,如果p 的后继不为空,就要考虑原本p 的后继的前驱
		p->next->prior = s;
	s->prior = p;						//再接入前驱
	p->next = s;						//最后才修改前驱的后继
	return true;
}

        删除

//删除p结点的后继结点
bool DeleteNextDNode(DNode* p)
{
	if (p == nullptr)			//判断所谓的p结点是否合法
		return false;
	DNode* q = p->next;			//设置一个指针指向后继结点,借此对后继结点进行操作
	if (q != nullptr)			//如果判断q == NULL,则需要写两次return true;会略显冗余
		p->next = q->next;
	delete q;
	return true;
}

//销毁双链表
bool DestoryList(DLinkList& L)
{
	while (L->next != nullptr)
		DeleteNextDNode(L);
	delete L;
	L = nullptr;				 //头指针最后要置空
}

iii.双链表的遍历

 //向后遍历

while(p != NULL)

{

        //常用于打印链表、计算链表长度等

        p = p->next;

        ...

}

/

//向前遍历

while(p != NULL)

{

        p = p->prior;

        ...

}

/

//向前遍历(不含头结点)

while(p->prior != NULL)       

{

        //最后会停在第一个有内容的结点处

        p = p->prior;

        ...

}

(3).循环链表

分为循环单链表和循环双链表,与普通链表的区别就在于最后一个结点会指向头结点,而非指向空。

i.循环单链表

//循环单链表
class LNode
{
public:
	int data;           
	LNode* next;        
};

using LinkList = LNode*;

//初始化
bool InitList(LinkList& L)
{
	L = new LNode();
	if (L == nullptr)   
		return false;
	L->next = L;		//与单链表的区别
	return true;
}
//判断是否为空
bool Empty(LinkList L)
{
	if (L->next == L)	//与单链表的区别
		return true;
	else
		return false;
}
//判断p是否在表尾
bool isTail(LinkList L, LNode* p)
{
	if (p->next == L)	//与单链表的区别
		return true;
	else
		return false;
}
//插入与删除,则可直接通过循环进行任意位置的操作

注:如果需要频繁地对表头和表尾进行操作时,可选用循环链表,设置一个指针指向表尾,即可实现最省时的表头表尾操作。

ii.循环双链表

        表头的前驱是表尾

        表尾的后继是表头

//循环双链表
class DNode
{
public:
	int data;				//数据域
	DNode* prior, * next;	//指针域
};

using DLinkList = DNode*;

//初始化
bool InitDList(DLinkList& L)
{
	L = new DNode();
	if (L == nullptr)		
		return false;
	L->prior = L;		
	L->next = L;
	return true;
}
//Empty函数、isTail函数与循环单链表相同

//在p结点后插入s结点(无需考虑结尾是否为空的问题)
bool InsertNextDNode(DNode* p, DNode* s)
{
	if (p == nullptr || s == nullptr)	
		return false;
	s->next = p->next;					
	p->next->prior = s;
	s->prior = p;						
	p->next = s;						
	return true;
}
//删除p结点的后继结点(无需考虑结尾是否为空的问题)
bool DeleteNextDNode(DNode* p)
{
	if (p == nullptr)			
		return false;
	DNode* q = p->next;			
	p->next = q->next;
	delete q;
	return true;
}

(4).静态链表 

#define MaxSize 10	//
//静态链表
class Node
{
public:
	int data;
	int next;
};

using SLinkList = Node[MaxSize];

        结点中,有两个元素,一个储存数据,一个储存数组下标.

        末尾结点中的数组下标标记为-1

        静态,指的就是链表可以储存的数据数量固定的,

        在定义删除时,为了防止脏数据(内存中原本储存的未知数据)等的干扰,要让空闲节点的数组下标为某个特殊值,如-2.

        不常用

        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值