前言
线性表是最常用且最简单的一种数据结构。简言之,一个线性表是n 个数据元素的有限序列。线性表在逻辑上是一种线性结构,也就是说连续的一条直线,但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构存储。
目录
二、线性表
2.1顺序表
2.1.1顺序表的实现
-
概念及结构
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构一般情况下采用数组存储。在数组上完成增删查改。
-
顺序表一般可以分为:
1.静态顺序表:使用定长数组存储。
#define N 100
typedef int SQDataType;//定义数据类型
typedef struct SeqList
{
SQDataType array[N];//数组长度
size_t size;//有效数据的个数
}SeqList;
静态顺序表的初始化、增、删、查、改等基本操作:
void SeqListInit(SeqList* psl)//初始化,传地址
{
memset(psl->a,0,sizeof(SQDataType)*N);
psl->size=0;
}
//头插
void SeQListPushBack(SeqList* psl,SQDataType x)
{
if(psl->size>=N)
{
printf("SeqList is Full");
return;
}
psl->a[psl->size]=x;
psl->size++;
}
问题:给少了不够用,给多了造成浪费
2.动态顺序表:使用动态开辟的数组存储。
typedef int SLDataType;
typedef struct SeqList
{
SLDataType* a;
int size;//有效数据的个数
int capacity;//容量
}SL;
动态顺序表的初始化、增、删、查、改等基本操作:
void SeqListInit(SL* ps)
{
ps->a = NULL;
ps->size = 0;
ps->capacity = 0;
}
void SeqListPushBack(SL* ps, SLDataType x)//尾插
{
SeqListCheak(ps);//检查是否需要扩容
ps->a[ps->size] = x;
ps->size++;
}
void SeqListPrint(SL* ps)//打印
{
assert(ps->size>0);
for (int i = 0;i < ps->size;++i)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
void SeqListCheak(SL* ps)//检查是否需要扩容
{
if (ps->size >= ps->capacity)
{
ps->capacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
ps->a = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * ps->capacity);
if (ps->a == NULL)
{
printf("扩容失败\n");
exit(-1);
}
}
}
void SeqListPopBack(SL* ps)//尾删
{
assert(ps->size>0);
//ps->a[ps->size - 1];//无意义
ps->size--;
}
void SeqListPushFront(SL* ps, SLDataType x)//头插
{
SeqListCheak(ps);
int end = ps->size-1;
while (end >= 0)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[0] = x;
ps->size++;
}
void SeqListPopFront(SL* ps)//头删
{
int start = 0;
while (start<=ps->size-1)
{
ps->a[start] = ps->a[start+1];
++start;
}
ps->size--;
}
void SeqListInsert(SL* ps, int pos, SLDataType x)//任意位置的插入删除
{
assert(pos<=ps->size);
SeqListCheak(ps);
int end = ps->size - 1;
while (end >= pos-1)
{
ps->a[end + 1] = ps->a[end];
--end;
}
ps->a[pos-1] = x;
ps->size++;
}
void SeqListErase(SL* ps, int pos)
{
assert(pos < ps->size);
int start = pos-1;
while (start <= ps->size - 1)
{
ps->a[start] = ps->a[start + 1];
++start;
}
ps->size--;
}
void SeqListFind(SL* ps, SLDataType x)//顺序表查找
{
for (int i = 0;i < ps->size;++i)
{
if (ps->a[i] == x)
{
printf("找到了,下标是%d \n", i);
return i;
}
}
return -1;
}
void SeqListSort(SL* ps)//顺序表的排序
{
for (int i = 0;i < ps->size - 1;++i)
{
int flag = 1;
for (int j = 0;j < ps->size - 1 - i;++j)
{
if (ps->a[j] > ps->a[j + 1])
{
int tmp = ps->a[j];
ps->a[j] = ps->a[j + 1];
ps->a[j + 1] = tmp;
flag = 0;
}
}
if (flag == 1)
break;
}
}
void SeqListBinaryFind(SL* ps,SLDataType x)//二分查找
{
int left = 0;
int right = ps->size-1;
int mid = 0;
while (left <= right)
{
mid = (left + right) / 2;
if (ps->a[mid] < x)
{
left = mid + 1;
}
else if (ps->a[mid]> x)
{
right = mid -1;
}
else
{
printf("找到了下标是 %d \n",mid);
break;
}
}
}
void SeqListDestroy(SL* ps)//顺序表摧毁
{
free(ps->a);
ps->size = 0;
ps->capacity = 0;
}
- 插入算法的时间复杂度分析:
问题的规模为表的长度n。算法的时间复杂度主要花费在向后移动元素的循环语句上。该语句重复执行*(n-i+1)*次。
2.1.2顺序表刷题
问题一:
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
来源:力扣(LeetCode)
int removeElement(int* nums, int numsSize, int val)
{
int res = 0;
for(int i = 0 ; i < numsSize; i++)
{
if(nums[i] == val)
{
}
else
{
nums[res] = nums[i];
res++;
}
}
return res;
}
思路分析:
用i遍历数组,用res指向第一个位置,如果不是val则把值给给res指向位置res++。
问题二:
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int* newNum=(int *)malloc(sizeof(int)*(m+n));
int i=0;
int j=0;
int min=0;
while(i<m&&j<n)
{
if(nums1[i]<nums2[j])
{
newNum[min++]=nums1[i++];
}
else
newNum[min++]=nums2[j++];
}
if(i<m)
{
while(i<m)
{
newNum[min]=nums1[i];
i++;
min++;
}
}
if(j<n)
{
while(j<n)
{
newNum[min]=nums2[j];
j++;
min++;
}
}
for(int k=0;k<m+n;++k)
{
nums1[k]=newNum[k];
}
}
来源:力扣(LeetCode)
思路分析:
开辟一个新的数组numNum,依此取出num1,num2中较小者,给给numNum,最后将num1或num2剩余元素给给numNum。
2.1.3顺序表分析
- 缺陷:
1.如果空间不够增容。会造成一定的性能消耗,其次会造出一定的空间浪费。
2.头部或者中间插入删除效率低。
2.2链表
链表:
- 空间上,按需给空间
- 不要求物理空间连续
2.2.1链表的概念及结构
概念:链表是一种物理存储结构上非连续、非顺序·的存储结构,数据元素的逻辑结构是通过链表中的指针链接次序实现的。
逻辑结构:
2.2.2单链表的实现及基本操作
- 单链表
typedef int SListDataType;
typedef struct SListNode
{
SListDataType data;
struct SListNode* next;
}SListNode;
- 基本操作
SListNode* BuySListNode(SListDataType x)//申请结点
{
SListNode* NewNode = (SListNode*)malloc(sizeof(SListNode));
if (NewNode == NULL)
{
printf("申请内存失败");
exit(-1);
}
NewNode->data = x;
NewNode->next = NULL;
return NewNode;
}
void SLPushBack(SListNode** pphead,SListDataType x)//链表尾插法
{
SListNode* NewNode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = NewNode;
}
else
{
SListNode* tail = *pphead;//找尾
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = NewNode;
}
}
void SLPushPop(SListNode** pphead)//链表尾删法
{
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* prev = NULL;
SListNode* tail = *pphead;
while (tail->next != NULL)
{
prev = tail;
tail = tail->next;
}
free(tail);
prev->next = NULL;
}
}
void SListPrint(SListNode* phead)//遍历打印链表
{
SListNode* cur = phead;
while (cur != NULL)
{
printf("%d->", cur->data);
cur = cur->next;
}
printf("NULL");
printf("\n");
}
void SListPushFront(SListNode** pphead, SListDataType x)//头插法
{
SListNode* NewNode = BuySListNode(x);
if (*pphead == NULL)
{
*pphead = NewNode;
}
else
{
//SListNode* head = *pphead;
NewNode->next = *pphead;
*pphead = NewNode;
}
}
void SListPopFront(SListNode** pphead)//头删法
{
if (*pphead == NULL)
{
return;
}
else if ((*pphead)->next == NULL)
{
free(*pphead);
*pphead = NULL;
}
else
{
SListNode* head = (*pphead)->next;
free(*pphead);
(*pphead)->next = NULL;
*pphead = head;
}
}
void SListDelete(SListNode** pphead,int i)//任意位置删除
{
SListNode* prev = NULL;
SListNode* cur = *pphead;
int size = 0;
while (size<=i)
{
prev = cur;
cur = cur->next;
++size;
}
assert(i >= 0 && i <= size);
if (*pphead == NULL)
{
return;
}
else
{
prev = cur->next;
free(cur);
cur->next = NULL;
}
}
单链表存在缺陷:
2.3双向链表
2.3.1双向链表基本介绍
2.3.2双向链表的实现
typedef int LTDataType;
typedef struct ListNode
{
struct ListNode* next;//尾指针
struct ListNode* prev;//头指针
LTDataType data;
}ListNode;
2.3.3双向链表的基本操作
void ListInit(ListNode** pphead)//初始化双向链表
{
*pphead = BuyListNode(0);
(*pphead)->next = *pphead;
(*pphead)->prev = *pphead;
}
void ListPushBack(ListNode* phead, LTDataType x)//尾插
{
assert(phead);//判断是否为空
ListNode* tail = phead->prev;
ListNode* newNode = BuyListNode(x);
tail->next = newNode;
newNode->prev = tail;
newNode->next = phead;
phead->prev = newNode;
}
ListNode* BuyListNode(LTDataType x)//创建新的结点
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
node->next = NULL;
node->prev = NULL;
node->data = x;
return node;
}
void ListNodePrint(ListNode* phead)//打印链表
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
printf("%d ", cur->data);
cur = cur->next;
}
printf("\n");
}
void ListPopBack(ListNode* phead)
{
assert(phead);
assert(phead->next != phead);
ListNode* tail = phead->prev;
ListNode* tailPrev = tail->prev;
free(tail);
tail = NULL;
phead->prev = tailPrev;
tailPrev->next = phead;
}
void ListPushFront(ListNode* phead, LTDataType x)//头插
{
assert(phead);
ListNode* first = phead->next;
ListNode* newNode = BuyListNode(x);
phead->next = newNode;
newNode->prev = phead;
first->prev = newNode;
newNode->next = first;
}
void ListPopFront(ListNode* phead)//头删
{
assert(phead);
assert(phead->next != phead);
ListNode* first = phead->next;
ListNode* second = first->next;
phead->next = second;
second->prev = phead;
free(first);
first = NULL;
}
ListNode* ListFind(ListNode* phead, LTDataType x)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
if (cur->data == x)
{
return cur;
}
cur = cur->next;
}
return NULL;
}
void ListInsert(ListNode* pos, LTDataType x)
{
assert(pos);
ListNode* posPrev = pos->prev;
ListNode* newNode = BuyListNode(x);
posPrev->next = newNode;
newNode->prev = posPrev;
newNode->next = pos;
pos->prev = newNode;
}
void ListErase(ListNode* pos)
{
assert(pos);
assert(pos->next != pos);
ListNode* posPrev = pos->prev;
ListNode* posNext = pos->next;
free(pos);
pos = NULL;
posPrev->next = posNext;
posNext->prev = posPrev;
}
void ListDestory(ListNode* phead)
{
assert(phead);
ListNode* cur = phead->next;
while (cur != phead)
{
ListNode* next = cur->next;
free(cur);
cur = next;
}
free(phead);
phead = NULL;
}
双向链表的结构虽然复杂了,但是操作反而简单了
2.3顺序表与链表分析
-
顺序表:
优点:
空间连续、支持随机访问
缺点:
中间或前面部分插入时间复杂度O(n)
增容代价较大 -
链表(带头双向循环)
缺点:以结点为储存单位,不支持随机访问
优点:
任意位置插入删除时间复杂度为O(1)
没有增容消耗,按需申请节点空间,不用了直接释放。