数据结构(顺序表OJ和单链表的实现)

顺序表OJ

1. 移除元素

思路:用双指针解决;首先创建两个指针dest和src指向0,当nums[src]等于val值的时候,让src++, 当nums[src]不等于val的时候就让nums[dest]=nums[src],dest++和src++,一直循环直到src<numsize最后再返回dest;由下图可见(有点简陋别建议)

代码如下:

int removeElement(int* nums, int numsSize, int val) 
{
    int dest = 0;
    int src = 0 ;
    while(src < numsSize)
    {
        if(nums[src]==val)
        {
            src++;
        }
        else
        {
            nums[dest++]=nums[src++];
        }
    }
    return dest;
}

2. 删除有序数组中的重复项

思路:定义两个指针/变量,dest指向第一个位置,src指向下一个位置,判断dest和src位置的数据,如果相等src++,如果不相等dest++,nums[dest]=nums[src],src++,最后再返回dest+1;但又因为不管是相等还是不相等都要src++所以可以让代码更简洁一些;

int removeDuplicates(int* nums, int numsSize) 
{
    int dest = 0;
    int src = 1;
    while(src<numsSize)
    {
        if(nums[dest]==nums[src])
        {
            src++;
        }
        else
        {
            dest++;
            nums[dest]=nums[src];
           src++;
        }
    }
    return dest+1;    
}

修改后如下:

int removeDuplicates(int* nums, int numsSize) 
{
    int dest = 0;
    int src = 1;
    while(src<numsSize)
    {
        if(nums[dest]!=nums[src])
        {
          dest++;
          nums[dest] = nums[src];
        }
        src++;
    }
    return dest+1;    
}

3. 合并两个有序数组

思路:创建三个指针/变量l1,l2,l3,l1指向第一个数组的最后一个有效数组的位置,l2指向第二个数组的最后一个有效数字的位置,l3指向第一个数组的最后一个位置;然后判断nums[l1]和nums[l2]的大小,如果nums[l2]>nums[l1],那么nums[l3--] = nums[l2--],否则nums[l3--]=nums[l1--],这是第一种结果也就是说l2遍历到0后;第二种情况:l1先遍历到下列序号为0的位置那么l2剩下的数据则全部放到l1里即可;

代码如下:

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) 
{
    int l1 = m-1;
    int l2 = n-1;
    int l3 = m+n-1;
    while(l1>=0&&l2>=0)
    {
        if(nums1[l1]>nums2[l2])
        {
            nums1[l3--]=nums1[l1--];
        }
        else
        {
            nums1[l3--]=nums2[l2--];
        }
    }
    while(l2>=0)
    {
        nums1[l3--]=nums2[l2--];
    }
}

单链表

1. 概念与结构

概念:链表是一种物理存储上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针连接次序实现的。

理解:链表其实就跟火车一样,火车的车厢是通过车钩一节一节连起来,而在链表中的“车厢”则被称为结点,在链表中没有车钩这一个说法,要想在一个结点内找到另一个结点则需要通过一个名为next的指针;如下图所示;(val就是存储你想要的元素,类似的是火车每个车厢不同的功能,例如有餐车等等)

  • 对于结点的解释:与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点” 结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。链表中每个结点都是独立申请的(即需要插入数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

2.链表的性质

  • 链式机构在逻辑上是连续的,在物理结构上不⼀定连续
  • 结点⼀般是从堆上申请的
  • 从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

结合前面的知识我们可以知道链表是由结点组成的,而结点的组成是由val(保存的数据)和指向下一个结点的next指针组成的,如下代码所示:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	//保存的val数据
	SLTDataType val;

	//指向下一个结点的next指针
	struct SListNode* next;

}SLTNode;

3. 单链表功能的实现

(1)实现链表的打印

一开始我们在打印顺序表的时候可以直接通过for循环创建i来代表数组下标进行依次打印,但链表不一样,链表不能通过此方法进行打印;实现打印链表的思路:对next指针合理使用,当打印完第一个结点的时候就通过next指针找到第二个结点进行打印,直到找到最后一个结点(最后结点的next指针指向NULL)的时候停下;代码如下:

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->val);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

代码的解释:创建同类型指针pcur指针变量保存第一个节点的地址,然后while循环(循环条件:当pcur==NULL的时候退出循环),打印当前pcur结点的元素,然后再让pcur指向下一个结点即可;

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果为:


(2)实现申请新结点

链表比顺序表的好处就在于我们需要多少个数据就申请多少个数据,不会造成数据的浪费,所以我们需要实现申请结点;实现申请结点的思路:接收链表所需的val数据,然后使用malloc申请一个同样大小的结点,再让该结点的next指针指向空;代码如下:

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->val = x;
	node->next = NULL;
	return node;
}

代码的解释:返回类型是SLTNode* 因为我们要创建一个同样类型的结点,首先SLTNode* 类型创建一个大小为SLTNode的node结点,然后判断node是否为空(这一步可以省略不写,因为申请结点的时候很小的概率会出现申请失败)如果node==NULL则报错且退出程序,然后让新创建的结点接收x,再让node指向下一个结点的指针next指向空,再返回node即可;

测试代码为:

void createSlist()
{
	SLTNode* node1 = SLTBuyNode(1);
	SLTNode* node2 = SLTBuyNode(2);
	SLTNode* node3 = SLTBuyNode(3);
	SLTNode* node4 = SLTBuyNode(4);
	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;
}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

调试结果为:

(3)实现尾插操作

尾插操作就是在链表的后面插入数据,也就是接上新的结点,但有种情况就是可能链表为空,那么在使用尾插的时候,尾插进来的数据就要成为头结点,这里的头结点的指向是会发生变化的,要改变指针的指向则需要用到二级指针;实现尾插操作的思路:首先申请一个新结点newnode,然后判断链表是否为空,如果为空则让申请的新结点成为头结点;如果链表不为空则进行尾插操作,创建一个结点找到尾结点再让尾结点的next指针指向申请的新结点newnode即可;代码如下:

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

下图便于理解二级指针:

测试代码为:


void createSlist()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPrint(plist);
}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(4)实现头插操作

实现头插的思路:创建新结点,让新结点的next指针指向头结点,再让头结点指向新结点;如下图和代码所示:

//从前插入
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

测试代码为:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(5)实现从后删除

从后删除就是把链表中的最后一个结点删掉,然后还要让最后一个结点的前一个结点的next指针成为尾结点,也就是让该结点的next指针指向空;这个也要做出判断,第一个判断:链表不能为空(链表为空没得删),第二个判断当只有一个节点的时候(上面说到了我们要找到最后一个结点的前一个结点,我们需要创建一个prev指针);实现从后删除的思路:创建一个ptail和prev指针,ptail去找到最后一个结点,而prev找到ptail的前一个结点,然后free掉ptail再让prev的next指针指向NULL即可;

代码如下:

//从后删除
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	//只有一个节点的时候
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}

实现代码为:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	//从后删除
	SLTPopBack(&plist);
	SLTPopBack(&plist);
	SLTPrint(plist);

}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:

代码解释:只有一个结点的时候那么*pphead的next指针肯定指向空,所以直接free掉*pphead就可以,如果不写这串代码就会程序崩溃,因为没有prev指针;else后就如上图所示,首先用ptail找到最后一个结点,然后prev找到ptail前的结点,然后把ptail free掉,再让prev的next指针指向NULL;


(6)实现从前删除

从前删除首先保证链表不为空,从头删除就是把头结点删掉,然后让pphead指向头结点的下一个结点,那我们就要把头结点的下一个结点保存下来,不然删掉了头结点后就找不到了;如下图和代码所示:

//从前删除
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* temp = (*pphead)->next;
	free(*pphead);
	*pphead = temp;
}

实现代码为:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	//从前删除
	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(7)实现指定数据查找

如果找到指定数据x,那就返回指向该数据所在的位置,如果找不到则返回NULL;实现思路:创建一个pcur指针遍历链表,当pcur所在的val等于指定数据x的时候就返回该地址,如果没找到则让pcur往前走,一直走到最后一个结点处;代码如下:

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->val== x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}	


(8)实现指定位置之前插入数据

在指定位置pos的之前插入数据,那么就会有三个数据收到影响,指定位置之前的结点、指定位置的结点、和要插入的数据的结点;也还有一种情况是指定位置是第一个结点那么就直接让插入的数据的next指针指向指定位置即可;实现思路:让newnode的next指针指向指定位置,再让指定位置前一个数据的next指针指向newnode,如果不按这个顺序的话就会出现找不到指定位置数据的情况如下图和代码所示:

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

实现代码:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* Find = SLTFind(plist, 3);
	SLTInsert(&plist, Find, 9);
	SLTPrint(plist);

}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(9)实现指定位置之后插入数据

在指定位置pos之后插入数据,同上面一样我们要保证链表不为空,且给过来的指定位置也不为空;在指定位置之后插入数据的话受影响的结点只有两个,一个是指定位置的结点另一个则是插入数据的结点;实现思路为:首先让插入的数据的结点的next 指向   指定位置的next指针指向的数据,再让指定位置的next指向新插入的结点;如下图和代码所示:

实现代码为:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* Find = SLTFind(plist, 3);
	SLTInsertAfter(Find, 9);
	SLTPrint(plist);

}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(10)实现删除指定位置结点

对于删除指定位置的结点,首先我们要保证链表不为空,传入的指定位置也不为空,当指定位置就是头结点的时候直接调用头删即可;实现思路:我们要找到被删除的那个结点的前一个结点,因为要让前一个结点的next指针 指向被删除结点next指针 指向的结点,而且我们要让前一个结点的next指针指向pos的next指针的数据,再free掉pos;如下图和代码所示:

实现代码如下:

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* Find = SLTFind(plist, 3);
	SLTErase(&plist, Find);
	SLTPrint(plist);

}

👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇  👇

运行结果:


(11)销毁链表

链表都是动态申请的结点组成的,所以我们也要全部主动销毁掉避免造成内存泄漏;实现思路:创建pcur指向pphead,创建一个next指针指向pcur指向pcur->next,free掉pcur,然后让pcur走到next的位置,一直循环直到pcur为NULL为止;如下图和代码所示:

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

最后还要把*pphead free掉;!


4.单链表实现的全部代码

slist.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>

typedef int SLTDataType;

typedef struct SListNode 
{
	//保存的val数据
	SLTDataType data;

	//指向下一个结点的next指针
	struct SListNode* next;

}SLTNode;

//打印链表
void SLTPrint(SLTNode* phead);

//申请新结点
SLTNode* SLTBuyNode(SLTDataType x);

//插入
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//删除
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** pphead);

slist.cpp

#include"Slist.h"

void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}


SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* node = (SLTNode*)malloc(sizeof(SLTNode));
	if (node == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	node->data = x;
	node->next = NULL;
	return node;
}


//从后插入
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* pcur = *pphead;
		while (pcur->next)
		{
			pcur = pcur->next;
		}
		pcur->next = newnode;
	}
}

//从前插入
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

//从后删除
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead && *pphead);
	//只有一个节点的时候
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* ptail = *pphead;
		SLTNode* prev = NULL;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
}
//从前删除
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* temp = (*pphead)->next;
	free(*pphead);
	*pphead = temp;
}


//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);
	SLTNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//没有找到
	return NULL;
}	

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}
//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos结点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);

	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}
//删除pos之后的结点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDestroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

test.cpp

#include"Slist.h"

void createSlist()
{
	SLTNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* Find = SLTFind(plist, 3);
	SLTErase(&plist, Find);
	SLTPrint(plist);

}

int main()
{
	createSlist();
	return 0;
}

END! 感谢

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值