线性表
2.线性表
2.1 线性结构和非线性结构
2.1.1 线性结构
1.线性结构是最常用的数据结构,其特点是元素之间存在一对一的线性关系。常见的线性结构有:数组,链表,队列和栈。
2.线性结构有两种不同的存储方式:顺序存储和链式存储。
3.顺序存储的线性表称为顺序表,其中存储的元素是连续的,支持随机访问。
4.链式存储的线性表称为链表,链表中的存储元素在内存中不一定是连续的,元素节点中存放数据元素以及其相邻元素的地址信息。
2.1.2 非线性结构
非线性结构有:二维数组,图,树,多维数组,广义表。
2.2 稀疏数组
当一个数组中的大部分元素为0或为同一个值时可以使用稀疏数组来保存该组数据。
稀疏数组的处理方法:
1.记录数组有几行几列以及多少个不同的值。
2.把具有不同值的元素的行列及值记录在一个小规模的数组中从而缩小程序的规模。
二维数组转稀疏数组的思路:
1.遍历原始的二维数组,得到有效数据的个数sum;
2.根据sum创建稀疏数组sparseArr int [sum+1] [3];
3.将二维数组的有效数据存入稀疏数组。
稀疏数组转回二维数组的思路:
1.读取稀疏数组的第一行数据创建原始的二维数组;
2.再读取稀疏数组后几行的数据并赋值给二维数据即可。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKzfP2Od-1679539930182)(2.线性表.assets\image-20221110161659639.png)]
代码实现:
void sparseArray(vector<vector<int>> chessArr1)
{
int sum = 0;
//先遍历二维数组来记录非0数字
for (int i = 0; i < chessArr1.size(); i++)
{
for (int j = 0; j < chessArr1[0].size(); j++)
{
if (chessArr1[i][j] != 0)
{
sum++;
}
}
}
//创建稀疏数组
vector<vector<int>> spraseArray1(sum + 1,vector<int>(3));
//给稀疏数组赋值
spraseArray1[0][0] = chessArr1.size(); //记录原二维数组的行数
spraseArray1[0][1] = chessArr1[0].size(); //记录原二维数组的列数
spraseArray1[0][2] = sum; //记录原二维数组的非零值个数
int count = 0;//记录非零值应该存入稀疏数组的第几行
for (int i = 0; i < chessArr1.size(); i++)
{
for (int j = 0; j < chessArr1[0].size(); j++)
{
if (chessArr1[i][j] != 0)
{
count++;
spraseArray1[count][0] = i;
spraseArray1[count][1] = j;
spraseArray1[count][2] = chessArr1[i][j];
}
}
}
cout << "得到的稀疏数组格式为:" << endl;
for (int i = 0; i < spraseArray1.size(); i++)
{
for (int j = 0; j < spraseArray1[0].size(); j++)
{
cout << spraseArray1[i][j]<<"\t";
}
cout << endl;
}
//将稀疏数组恢复为二维数组
vector<vector<int>> chessVec(spraseArray1[0][0], vector<int>(spraseArray1[0][1]));
int hang = 0;
int lie = 0;
for (int i = 1; i < spraseArray1.size(); i++)
{
chessVec[spraseArray1[i][0]][spraseArray1[i][1]] = spraseArray1[i][2];
}
cout << "恢复后的二维数组为:" << endl;
for (int i = 0; i < chessVec.size(); i++)
{
for (int j = 0; j < chessVec[0].size(); j++)
{
cout << chessArr1[i][j] << "\t";
}
cout << endl;
}
}
2.3 队列
1.线性表,有序列表,可以用数组或者链表来实现。
2.遵循先进先出的特性,如银行排队等场景。
2.3.1 队列的数组实现
使用变量front和rear分别记录队列前后端的下标,front指向队列头部的前一个位置,随着数据输出而改变,rear指向队列尾部,随着输入而改变。
输入数据时判断rear==maxSize-1,若不相等则可输入数据,若相等代表队列已满,不可输入数据。
数组代码实现:
class QueueDemo
{
private:
int* arr;
int maxSize;
int front;
int rear;
public:
QueueDemo(int maxSize)
{
this->maxSize = maxSize;
this->arr = new int[maxSize];
this->front = -1;
this->rear = -1;
}
//判断是否为空
bool isEmpty()
{
return front == rear;
}
//判断是否已满
bool isFull()
{
return rear==maxSize-1;
}
//添加数据
void addQueue(int n)
{
if (isFull())
{
cout << "队列已满,无法添加!" << endl;
return;
}
arr[++rear] = n;
}
//数据出列
int getQueue()
{
if (isEmpty())
{
cout << "队列中无数据!" << endl;
return 0;
}
return arr[++front];
}
//打印所有数据
void printQueue()
{
if (isEmpty())
{
cout << "队列中无数据!" << endl;
return;
}
cout << "队列总共的数据量为:" << rear - front;
cout << "数据分别为:" << endl;;
for (int i = front+1; i <= rear; i++)
{
cout << arr[i];
cout << endl;
}
}
};
数组实现过程中会出现一个问题:当存入数据满后,即使出列使逻辑空间不为满,但是在代码实现中仍不能够入列。
STL中可直接使用queue容器创建队列
void QueueVecDemo()
{
queue<int> q; //创建队列
q.push(1); //入队列
q.pop(); //出队列
q.front(); //返回队列首元素
q.back(); //返回队列尾元素
q.size(); //获取队列大小
q.empty(); //如果队列空则返回true
}
2.3.2数组模拟环形队列
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rPEF7ZKZ-1679539930183)(2.线性表.assets/image-20221201161051077.png)]
思路:
1.front变量含义调整为:front指向队列的第一个元素,初始值为0,即arr[front]就是队列的第一个元素;
2.rear变量含义调整为:rear指向队列最后一个元素的后一个位置,初始值为0,留一个空白空间作为约定;
3.当队列满时,条件为(rear+1)%(maxSize)==front;
4.当队列为空时,条件为rear==front;
5.队列中有效数据为(rear+maxSize-front)%maxSize。
代码实现:
class circleArr
{
private:
int* arr;
int maxSize;
int front;
int rear;
public:
circleArr(int maxSize)
{
this->maxSize = maxSize;
this->arr = new int[maxSize];
this->front = 0;
this->rear = 0;
}
bool isFull
{
return (rear + 1) % maxSize == front;
}
bool isEmpty()
{
return front == rear;
}
//添加数据
void addQueue(int n)
{
if (isFull())
{
cout << "队列已满,无法添加!" << endl;
return;
}
arr[rear] = n;
rear = (rear + 1) % maxSize;
}
//数据出列
int getQueue()
{
if (isEmpty())
{
cout << "队列中无数据!" << endl;
return 0;
}
int temp = arr[front];
front = (front + 1) % maxSize;
return temp;
}
//打印所有数据
void printQueue()
{
if (isEmpty())
{
cout << "队列中无数据!" << endl;
return;
}
cout << "队列总共的数据量为:" << (rear + maxSize - front) % maxSize << endl;
cout << "数据分别为:" << endl;
int num = 1;
for (int i = front; i < front+(rear + maxSize - front) % maxSize; i++)
{
cout << "第" << num++ << "个数据为" << arr[i % maxSize];
cout << endl;
}
}
int headQueue()
{
if (isEmpty())
{
cout << "队列为空" << endl;
}
return arr[front];
}
};
int main()
{
circleArr *arr = new circleArr(5);
cout << "\t测试\t" << endl;
bool stop = true;
while (stop)
{
cout << "s:显示队列" << endl;
cout << "e:退出队列" << endl;
cout << "a:添加数据到队列" << endl;
cout << "g:取出数据在队列" << endl;
cout << "h:查看队列头" << endl;
char str;
cin>>str;
switch (str)
{
case 's':
arr->printQueue();
break;
case 'e':
stop=0;
break;
case 'a':
int input;
cout << "请输入要添加的数据:";
cin >> input;
arr->addQueue(input);
break;
case 'g':
arr->getQueue();
break;
case 'h':
cout << "队列头数据为:" << arr->headQueue() << endl;
break;
default:
break;
}
}
return 0;
}
2.4 链表
2.4.1 链表介绍
链表是链式存储的线性表,不支持随机访问。链表又分为带头节点和不带头节点两类。
1.链表以节点的方式存储数据;
2.每个节点包含data域,next域,其中data域存放本节点的数据,next域存放下一个节点的地址;
3.链表的各个节点不一定是连续的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bIVXQCL2-1679539930183)(2.线性表.assets/image-20221201202541797.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xhFKk1XL-1679539930184)(2.线性表.assets/image-20221201202711149.png)]
2.4.2单链表的实现
代码如下:
#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;
typedef struct LinkedNode
{
int data;
LinkedNode* next;
}LinkedNode,* LinkedList; //HeroNode强调单个节点,LinkedList强调链表本身
//初始化一个带头节点的单链表
LinkedList InitList()
{
LinkedList head= new LinkedNode;
head->next = NULL;
return head;
}
//判空
bool isEmptyHaveHead(LinkedList& List)
{
return List->next == NULL;
}
//获取链表长度
int GetLength(LinkedList& L)
{
int length = 0;
LinkedNode* node = L->next;
while (node != NULL)
{
node = node->next;
length++;
}
cout << "此链表长度:" << length << endl;
return length;
}
//按位查找
LinkedNode* haveHeadGetByIndex(LinkedList& list, int index)
{
if (isEmptyHaveHead(list))
{
cout << "链表为空" << endl;
return NULL;
}
if (index < 0)
{
cout << "输入不合法" << endl;
return NULL;
}
//声明临时节点供查找,不需要存储空间
LinkedNode* node = list->next;
int i = 0;
while (node != NULL && i < index)
{
node = node->next;
i++;
}
return node;
}
//按值查找
LinkedNode* haveHeadGetByData(LinkedList& list, int data)
{
if (isEmptyHaveHead(list))
{
cout << "链表为空" << endl;
return NULL;
}
LinkedNode* node = list->next;
while (node != NULL && node->data != data)
{
node = node->next;
}
return node;
}
//头插法建立单链表
void List_HeadInsert(LinkedList& list)
{
int dataInput;
cin >> dataInput;
while(dataInput != 9999)
{
LinkedNode* node = new LinkedNode;
node->data = dataInput;
node->next = list->next;
list->next = node;
cin >> dataInput;
}
}
//尾插法
LinkedList List_TailInsert(LinkedList& list)
{
LinkedNode* rear = list;
int dataInput;
cin >> dataInput;
while (dataInput != 9999)
{
LinkedNode* node = new LinkedNode;
node->data = dataInput;
node->next = rear->next;
rear->next = node;
rear = node;
cin >> dataInput;
}
rear->next = NULL;
return list;
}
// 前插操作:在p结点之前插入数据e,重点,实际节点是插入到了p的后边,但是交换了数据
bool InsertPriorNode(LinkedNode* p, int e) {
if (p == NULL) {
return false;
}
LinkedNode* q = new LinkedNode;
q->next = p->next;
q->data = p->data;
p->data = e;
p->next = q;
return true;
}
// 后插操作:在p结点之后插入数据e
bool InsertNextNode(LinkedNode* p, int e) {
if (p == NULL) {
return false;
}
LinkedNode* q = new LinkedNode;
q->data = e;
q->next = p->next;
p->next = q;
return true;
}
// 删除p结点的后继结点
bool DeleteNextDNode(LinkedNode* p) {
if (p == NULL || p->next == NULL) {
return false;
}
LinkedNode* s = new LinkedNode;
s = p->next;
p->next = s->next;
delete s;
return true;
}
// 删除指定结点 重点,实际删除了p节点的后一个 知识将后一个节点数据赋值给当前节点
bool DeleteNode(LinkedNode* p) {
if (p == NULL) {
return false;
}
LinkedNode* s = new LinkedNode;
s = p->next;
p->data = s->data;
p->next = s->next;
delete s;
return true;
}
// 遍历单链表
void TraverseList(LinkedList& L) {
if (L->next == NULL) {
return;
}
LinkedNode* p = L->next; // 指向头指针
while (p != NULL) {
cout << p->data << " ";
p = p->next;
}
cout << endl;
}
int main()
{
LinkedList L= InitList();
List_TailInsert(L);
return 0;
}
反转链表思路
方法一
将头结点指向null; 变成最后一个结点;提前把第2保存在temp中。第二个结点指向第一个结点…结束条件是cur=null
public Node reverseList(Node head) {
Node temp;
Node cur;
Node pre;
pre = null;
cur = head;
while
(cur != null) {
temp = cur.next;
cur.next = pre;
pre = cur;
cur = temp;
}
return pre;
}
方法二:递归
找到结束递归的方法:当head或者head.next=null时,它的反转链表就是head
用链表1,2,3,4举例:
ListNode* reverseList(ListNode* head) {
if (head == NULL || head->next == NULL) return head;
// 递归结束条件
ListNode* newNode = reverseList(head->next);
//第一次递归结束,返回的head=4;
head->next->next = head;
//返回上层递归 当head.next=3,传入reverseList2时head=3;
(head.next.next = head)
//也就是说明4的下一个结点是3
head->next = NULL;
//将3的下一个结点指向null 即将原来的3与4之间的连接断开
return newNode;
}
2.4.3双向链表的实现
#include <iostream>
#include<cstring>
using namespace std;
class ListNode
{
public:
char data;
ListNode* prev;
ListNode* next;
ListNode()
{
data = '0';
prev = nullptr;
next = nullptr;
}
ListNode(int x)
{
data = x;
prev = nullptr;
next = nullptr;
}
};
class DoubleLinkedList
{
public:
void InsertElement(int, char);
void CreatList(const char*);
void SequentialPrint(void);
void ReversePrint(void);
void DeleteElement(int);
DoubleLinkedList()
{
head = new ListNode;
tail = new ListNode;
length = 0;
head->data = '0';
head->next = tail;
head->prev = nullptr;
tail->data = '0';
tail->prev = head;
tail->next = nullptr;
}
ListNode* head;
ListNode* tail;
private:
int length = 0;
};
void DoubleLinkedList::InsertElement(int position, char element)
{
if (position <1 || position>length + 1)
{
return;
}
ListNode* p = head;
for (int i = 1; i < position; i++)
{
p = p->next;
}
ListNode* q = p->next;
ListNode* tmp = new ListNode(element);
length++;
tmp->prev = p;
p->next = tmp;
tmp->next = q;
q->prev = tmp;
}
void DoubleLinkedList::CreatList(const char* str)
{
int x_len = strlen(str);
for (int i = 0; i < x_len; i++)
{
this->InsertElement(i + 1, str[i]);
}
}
void DoubleLinkedList::SequentialPrint()
{
if (length == 0)
{
return;
}
ListNode* p = head->next;
while (p->next)
{
cout << p->data;
p = p->next;
}
cout << endl;
}
void DoubleLinkedList::ReversePrint()
{
if (length == 0)
{
return;
}
ListNode* p = tail->prev;
while (p->prev)
{
cout << p->data;
p = p->prev;
}
cout << endl;
}
void DoubleLinkedList::DeleteElement(int location)
{
if (location > length || location < 1)
{
return;
}
ListNode* p = head;
if (location == length)
{
for (int i = 1; i < location; i++)
{
p = p->next;
}
ListNode* q = p->next;
delete q;
length--;
p->next = this->tail;
this->tail = p;
}
else
{
for (int i = 1; i < location; i++)
{
p = p->next;
}
ListNode* q1 = p->next;
ListNode* q2 = p->next->next;
delete q1;
length--;
p->next = q2;
q2->prev = p;
}
}
bool Judge_HUIWEN(DoubleLinkedList x)
{
ListNode* p = x.head;
ListNode* q = x.tail;
while (true)
{
if ((p == q) || (p->prev == q))return 1;
if (p->data != q->data)return 0;
p = p->next;
q = q->prev;
}
}
int main() {
DoubleLinkedList obj;
char temp[105] = {};
cin >> temp;
obj.CreatList(temp);
if (Judge_HUIWEN(obj))
{
obj.SequentialPrint();
cout << "True" << endl;
}
else
{
obj.SequentialPrint();
cout << "False" << endl;
}
system("pause");
return 0;
}